17 Commits

Author SHA1 Message Date
Mark VanderVoord df5ba4f2fc Merge branch 'master' into bugfix/interactions 2026-06-26 15:28:54 -04:00
Mark VanderVoord d141b59324 Merge pull request #533 from ptrbman/fix-temperature-typo
[typo] Fix misspelling in temperature test name
2026-06-26 15:28:26 -04:00
Mark VanderVoord b992f1f2e9 Verify that failures can be thrown from tearDown without crashing (Verify #67) 2026-06-26 15:25:41 -04:00
Mark VanderVoord e851a39d58 Make sure that calling conventions within the argument list do not break parsing (Fixes #51) 2026-06-26 14:44:57 -04:00
Mark VanderVoord dc34a9000c Add validation that last commit also fixed #94 2026-06-26 14:29:34 -04:00
Mark VanderVoord 55dd1f1ce5 Protect against function-looking pointers inside struct definitions (Fix #399) 2026-06-26 14:22:13 -04:00
Mark VanderVoord 9e8d1f93ef Fixed handling of pointers that look like function pointers but are not (Fixed #349) 2026-06-26 13:00:26 -04:00
Mark VanderVoord 783bbde34d Verify that const qualifiers are retained with custom types (verifies #484) 2026-06-26 12:17:24 -04:00
Mark VanderVoord 92debabbf0 Add CMOCK_MEMCPY and CMOCK_MEMSET and refactor internal handling of pointer data to fix #11 & #244. 2026-06-26 11:56:07 -04:00
Mark VanderVoord d20d18e75c Verify that our handling of const and pointers now preserve order properly (Verifies #485) 2026-06-26 11:14:10 -04:00
Mark VanderVoord 5f74197056 The ReturnThrPtr plugin now works even when using Ignore (Fix #61, #225) 2026-06-26 10:15:07 -04:00
Peter Backeman 6e048b07ff [typo] Fix misspelling in temperature test name
Rename testShouldInitializeTemeratureToInvalidValue to
testShouldInitializeTemperatureToInvalidValue.
2026-06-22 22:21:15 +02:00
Mark VanderVoord d482f56066 Protect against more function-looking macros (Fixes#502) 2026-06-19 15:58:08 -04:00
Mark VanderVoord 1ef72ca854 Protect against function-looking structs (Fixes #513) 2026-06-19 15:10:14 -04:00
Mark VanderVoord 85058d1407 Fixed skeleton path handling, etc (#488) 2026-06-19 14:42:09 -04:00
Mark VanderVoord 411f6852f9 Add documentation to avoid problems like #518 and #521. 2026-06-19 13:50:21 -04:00
Mark VanderVoord 383c43246c Merge pull request #531 from ThrowTheSwitch/feature/better_arrays
Feature/better arrays
2026-06-19 10:53:54 -04:00
21 changed files with 1217 additions and 30 deletions
+40
View File
@@ -378,6 +378,23 @@ generate a skeleton instead:
ruby cmock.rb --skeleton ../create/c/for/this.h
```
Using CMock Without Ceedling
----------------------------
CMock depends on the Unity test framework, but it does not *require* Ceedling. You can use the
generated mocks directly with the Unity test framework in whatever build system you prefer. One
important thing to remember when doing this is that you will need to call the `_Init` function
for each of your mocks BEFORE the tests and the `_Verify` function for each mock AFTER each test.
This allows CMock to perform all of its internal accounting. If you're running into problems where
some errors aren't getting caught, this is likely what you are missing.
There are many ways to accomplish this. Any is valid:
- These actions can be performed as part of `setUp` and `tearDown` in each test file
- You can hand-write your own RUN_TEST macro. If so, protect `_Verify` calls in `TEST_PROTECT`
- You can use Unity's test runner generator and it will automatically take care of this for you.
- You can use Ceedling and it will automatically take care of this for you.
Config Options:
---------------
@@ -864,6 +881,29 @@ based on other settings, particularly Unity's settings.
This needs to be something big enough to point anywhere in Cmock's
memory space... usually it's a size_t.
* `CMOCK_MEMCPY`
The memory-copy function used by CMock's internals and the generated mocks.
It defaults to `memcpy` from `<string.h>`. Override alongside `CMOCK_MEMSET`
to supply a custom implementation on targets where the standard library is
unavailable or undesirable:
```c
#define CMOCK_MEMCPY(dst, src, size) my_memcpy(dst, src, size)
```
* `CMOCK_MEMSET`
The memory-set function used by CMock's internals to zero-initialize
allocated blocks. It defaults to `memset` from `<string.h>`. Override
alongside `CMOCK_MEMCPY` to keep CMock fully independent of the standard
library:
```c
#define CMOCK_MEMSET(dst, val, size) my_memset(dst, val, size)
```
If both `CMOCK_MEMCPY` and `CMOCK_MEMSET` are defined before including
`cmock.h`, CMock will not pull in `<string.h>` at all.
Other Tips
==========
@@ -19,7 +19,7 @@ void tearDown(void)
{
}
void testShouldInitializeTemeratureToInvalidValue(void)
void testShouldInitializeTemperatureToInvalidValue(void)
{
TemperatureFilter_Init();
TEST_ASSERT_FLOAT_WITHIN(0.0001f, -INFINITY, TemperatureFilter_GetTemperatureInCelcius());
+37
View File
@@ -16,6 +16,17 @@ class CMockFileWriter
require 'fileutils'
FileUtils.mkdir_p "#{@config.mock_path}/" unless Dir.exist?("#{@config.mock_path}/")
FileUtils.mkdir_p "#{@config.mock_path}/#{"#{subdir}/" if subdir}" if subdir && !Dir.exist?("#{@config.mock_path}/#{"#{subdir}/" if subdir}")
rescue SystemCallError => e
raise "Unable to create mock output directory: #{e.message}. Check :mock_path ('#{@config.mock_path}') configuration."
end
def create_skeleton_subdir(subdir)
require 'fileutils'
base = effective_skeleton_path
FileUtils.mkdir_p base
FileUtils.mkdir_p "#{base}/#{subdir}" if subdir
rescue SystemCallError => e
raise "Unable to create skeleton output directory: #{e.message}. Check :skeleton_path ('#{base}') configuration."
end
def create_file(filename, subdir)
@@ -27,6 +38,27 @@ class CMockFileWriter
yield(file, filename)
end
update_file(full_file_name_done, full_file_name_temp)
rescue SystemCallError => e
raise "Unable to write mock file '#{full_file_name_done}': #{e.message}. Check :mock_path ('#{@config.mock_path}') and :subdir ('#{subdir}') configuration."
end
def create_skeleton_file(filename, subdir)
raise "Where's the block of data to create?" unless block_given?
base = effective_skeleton_path
full_file_name_temp = "#{base}/#{"#{subdir}/" if subdir}#{filename}.new"
full_file_name_done = "#{base}/#{"#{subdir}/" if subdir}#{filename}"
File.open(full_file_name_temp, 'w') do |file|
yield(file, filename)
end
update_file(full_file_name_done, full_file_name_temp)
rescue SystemCallError => e
raise "Unable to write skeleton file '#{full_file_name_done}': #{e.message}. Check :skeleton_path ('#{base}') and :subdir ('#{subdir}') configuration."
end
def skeleton_file_path(filename, subdir)
base = effective_skeleton_path
"#{base}/#{"#{subdir}/" if subdir}#{filename}"
end
def append_file(filename, subdir)
@@ -40,6 +72,11 @@ class CMockFileWriter
private ###################################
def effective_skeleton_path
path = @config.skeleton_path
path.nil? || path.empty? ? @config.mock_path : path
end
def update_file(dest, src)
require 'fileutils'
FileUtils.rm(dest, :force => true)
+28 -20
View File
@@ -85,6 +85,7 @@ class CMockGenerator
:skeleton => true
}
@file_writer.create_skeleton_subdir(@subdir)
create_skeleton_source_file(mock_project)
end
@@ -133,9 +134,9 @@ class CMockGenerator
end
def create_skeleton_source_file(mock_project)
filename = "#{@config.mock_path}/#{"#{@subdir}/" if @subdir}#{mock_project[:module_name]}.c"
filename = @file_writer.skeleton_file_path("#{mock_project[:module_name]}.c", @subdir)
existing = File.exist?(filename) ? File.read(filename) : ''
@file_writer.create_file("#{mock_project[:module_name]}.c", @subdir) do |file, fullname|
@file_writer.create_skeleton_file("#{mock_project[:module_name]}.c", @subdir) do |file, fullname|
blank_project = mock_project.clone
blank_project[:parsed_stuff] = { :functions => [] }
if existing.empty?
@@ -210,24 +211,30 @@ class CMockGenerator
def create_source_header_section(file, filename, mock_project)
header_file = (mock_project[:folder] || '') + filename.sub(/.*\K\.c/, mock_project[:module_ext])
file << "/* AUTOGENERATED FILE. DO NOT EDIT. */\n" unless mock_project[:parsed_stuff][:functions].empty?
file << "#include <string.h>\n"
file << "#include <stdlib.h>\n"
unless @exclude_setjmp_h
file << "#include <setjmp.h>\n"
end
file << "#include \"cmock.h\"\n"
@includes_c_pre_header.each { |inc| file << "#include #{inc}\n" }
file << "#include \"#{header_file}\"\n"
@includes_c_post_header.each { |inc| file << "#include #{inc}\n" }
file << "\n"
strs = []
mock_project[:parsed_stuff][:functions].each do |func|
strs << func[:name]
func[:args].each { |arg| strs << arg[:name] }
end
strs.uniq.sort.each do |str|
file << "static const char* CMockString_#{str} = \"#{str}\";\n"
if mock_project[:skeleton]
@includes_c_pre_header.each { |inc| file << "#include #{inc}\n" }
file << "#include \"#{header_file}\"\n"
@includes_c_post_header.each { |inc| file << "#include #{inc}\n" }
else
file << "/* AUTOGENERATED FILE. DO NOT EDIT. */\n" unless mock_project[:parsed_stuff][:functions].empty?
file << "#include <string.h>\n"
file << "#include <stdlib.h>\n"
unless @exclude_setjmp_h
file << "#include <setjmp.h>\n"
end
file << "#include \"cmock.h\"\n"
@includes_c_pre_header.each { |inc| file << "#include #{inc}\n" }
file << "#include \"#{header_file}\"\n"
@includes_c_post_header.each { |inc| file << "#include #{inc}\n" }
file << "\n"
strs = []
mock_project[:parsed_stuff][:functions].each do |func|
strs << func[:name]
func[:args].each { |arg| strs << arg[:name] }
end
strs.uniq.sort.each do |str|
file << "static const char* CMockString_#{str} = \"#{str}\";\n"
end
end
file << "\n"
end
@@ -339,6 +346,7 @@ class CMockGenerator
file << " UNITY_SET_DETAIL(CMockString_#{function[:name]});\n"
file << " cmock_call_instance = (CMOCK_#{function[:name]}_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.#{function[:name]}_CallInstance);\n"
file << " Mock.#{function[:name]}_CallInstance = CMock_Guts_MemNext(Mock.#{function[:name]}_CallInstance);\n"
file << @plugins.run(:mock_precheck_return_thru_ptr, function)
file << @plugins.run(:mock_implementation_precheck, function)
file << " UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringCalledMore);\n"
file << " cmock_line = cmock_call_instance->LineNumber;\n"
+39 -1
View File
@@ -13,6 +13,8 @@ class CMockGeneratorPluginReturnThruPtr
@utils = utils
@priority = 9
@config = config
plugins = @config.plugins
@ignore_used = plugins.include?(:ignore) || plugins.include?(:ignore_stateless)
end
def ptr_to_const(arg_type)
@@ -69,6 +71,25 @@ class CMockGeneratorPluginReturnThruPtr
lines
end
def mock_precheck_return_thru_ptr(function)
return '' unless @ignore_used
lines = []
function[:args].each do |arg|
arg_name = arg[:name]
next unless @utils.ptr_or_str?(arg[:type]) && !(arg[:const?])
lines << " if (Mock.#{function[:name]}_IgnoreBool && cmock_call_instance != NULL &&\n"
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Used)\n"
lines << " {\n"
lines << " UNITY_TEST_ASSERT_NOT_NULL(#{arg_name}, cmock_line, CMockStringPtrIsNULL);\n"
lines << " CMOCK_MEMCPY((void*)#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Size);\n"
lines << " }\n"
end
lines
end
def mock_interfaces(function)
lines = []
func_name = function[:name]
@@ -80,6 +101,23 @@ class CMockGeneratorPluginReturnThruPtr
lines << "{\n"
lines << " CMOCK_#{func_name}_CALL_INSTANCE* cmock_call_instance = " \
"(CMOCK_#{func_name}_CALL_INSTANCE*)CMock_Guts_GetAddressFor(CMock_Guts_MemEndOfChain(Mock.#{func_name}_CallInstance));\n"
if @ignore_used
lines << " if (Mock.#{func_name}_IgnoreBool &&\n"
lines << " (cmock_call_instance == NULL || cmock_call_instance->ReturnThruPtr_#{arg_name}_Used))\n"
lines << " {\n"
lines << " CMOCK_MEM_INDEX_TYPE cmock_guts_index = CMock_Guts_MemNew(sizeof(CMOCK_#{func_name}_CALL_INSTANCE));\n"
lines << " CMOCK_#{func_name}_CALL_INSTANCE* new_instance = (CMOCK_#{func_name}_CALL_INSTANCE*)CMock_Guts_GetAddressFor(cmock_guts_index);\n"
lines << " UNITY_TEST_ASSERT_NOT_NULL(new_instance, cmock_line, CMockStringOutOfMemory);\n"
lines << " memset(new_instance, 0, sizeof(*new_instance));\n"
lines << " new_instance->LineNumber = cmock_line;\n"
unless function[:return][:void?]
lines << " if (cmock_call_instance != NULL)\n"
lines << " new_instance->ReturnVal = cmock_call_instance->ReturnVal;\n"
end
lines << " Mock.#{func_name}_CallInstance = CMock_Guts_MemChain(Mock.#{func_name}_CallInstance, cmock_guts_index);\n"
lines << " cmock_call_instance = new_instance;\n"
lines << " }\n"
end
lines << " UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringPtrPreExp);\n"
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Used = 1;\n"
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Val = #{arg_name};\n"
@@ -98,7 +136,7 @@ class CMockGeneratorPluginReturnThruPtr
lines << " if (cmock_call_instance->ReturnThruPtr_#{arg_name}_Used)\n"
lines << " {\n"
lines << " UNITY_TEST_ASSERT_NOT_NULL(#{arg_name}, cmock_line, CMockStringPtrIsNULL);\n"
lines << " memcpy((void*)#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
lines << " CMOCK_MEMCPY((void*)#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Size);\n"
lines << " }\n"
end
+33 -2
View File
@@ -251,14 +251,27 @@ class CMockHeaderParser
source.gsub!(/^\s*#.*/, '')
# enums, unions, structs, and typedefs can all contain things (e.g. function pointers) that parse like function prototypes, so yank them
# pre-collapse nested brace pairs so that structs containing nested structs/unions are removed as a unit below
source = remove_nested_pairs_of_braces(source) unless cpp
# forward declared structs are removed before struct definitions so they don't mess up real thing later. we leave structs keywords in function prototypes
source.gsub!(/^[\w\s]*struct[^;{}()]+;/m, '') # remove forward declared structs
source.gsub!(/^[\w\s]*(enum|union|struct|typedef)[\w\s]*\{[^}]+\}[\w\s*,]*;/m, '') # remove struct, union, and enum definitions and typedefs with braces
source.gsub!(/^[\w\s]*(enum|union|struct|typedef)[\w\s()]*\{[^}]+\}[\w\s*,]*;/m, '') # remove struct, union, and enum definitions and typedefs with braces
# remove problem keywords
source.gsub!(/(\W)(?:register|auto|restrict)(\W)/, '\1\2')
source.gsub!(/(\W)(?:static)(\W)/, '\1\2') unless cpp
source.gsub!(/\s*=\s*['"a-zA-Z0-9_.]+\s*/, '') # remove default value statements from argument lists
# strip macro decorator patterns that cannot be C function prototypes.
# must run after default-value removal so "= \"str\"" doesn't trigger string detection.
# neutralize string literals (never valid in C prototype args), eliminating any parentheses
# inside string content (e.g. "msg()") that would fool subsequent brace-matching.
source.gsub!(/"[^"]*"/, '""')
# strip WORD("") -- any call with a string literal arg cannot be a C function prototype
source.gsub!(/\b\w+\s*\([^)]*""[^)]*\)/, '')
# strip WORD(N...) -- any call whose first arg starts with a digit cannot be a C prototype
source.gsub!(/\b\w+\s*\(\s*\d[^)]*\)/, '')
source.gsub!(/^(?:[\w\s]*\W)?typedef\W[^;]*/m, '') # remove typedef statements
source.gsub!(/\)(\w)/, ') \1') # add space between parenthese and alphanumeric
source.gsub!(/(^|\W+)(?:#{@c_strippables.join('|')})(?=$|\W+)/, '\1') unless @c_strippables.empty? # remove known attributes slated to be stripped
@@ -554,6 +567,10 @@ class CMockHeaderParser
# pull asterisks away from arg to place asterisks with type (where they belong)
arg_list.gsub!(/\*(\w)/, '* \1')
# normalize parenthesized pointer arguments like int (* numb) -> int * numb
# negative lookahead prevents matching function pointers (*name)(args) and pointer-to-arrays (*name)[dims]
arg_list.gsub!(/\(\s*\*\s*((?:const\s+)?\w+)\s*\)(?!\s*[(\[])/, '* \1')
# scan argument list for function pointers and replace them with custom types
arg_list.gsub!(/([\w\s*]+)\(+([\w\s]*)\*[*\s]*([\w\s]*)\s*\)+\s*\(((?:[\w\s*]*,?)*)\s*\)*/) do |_m|
functype = "cmock_#{parse_project[:module_name]}_func_ptr#{parse_project[:typedefs].size + 1}"
@@ -581,11 +598,24 @@ class CMockHeaderParser
funcname = Regexp.last_match(2).strip
funcargs = Regexp.last_match(3).strip
funconst = ''
funcdecl = ''
if funcname.include? 'const'
funcname.gsub!('const', '').strip!
funconst = 'const '
end
parse_project[:typedefs] << "typedef #{funcret}(*#{functype})(#{funcargs});"
# Extract any calling convention from the return type (it belongs in the function pointer declaration)
@c_calling_conventions.each do |cc|
next unless funcret.include?(cc)
funcret = funcret.gsub(cc, '').strip
funcdecl = cc
break
end
parse_project[:typedefs] << if funcdecl.empty?
"typedef #{funcret}(*#{functype})(#{funcargs});"
else
"typedef #{funcret}(#{funcdecl} *#{functype})(#{funcargs});"
end
funcname = "cmock_arg#{c += 1}" if funcname.empty?
"#{functype} #{funconst}#{funcname}"
end
@@ -636,6 +666,7 @@ class CMockHeaderParser
rettype = parsed[:type]
rettype = 'void' if @local_as_void.include?(rettype.strip)
rettype = 'void' if rettype.empty? && !(@standards + @local_as_void).include?(parsed[:name]) # all return-type tokens were stripped (e.g. bare decorator macros)
retstr = parsed[:const_ptr?] ? "#{rettype} const" : rettype
decl[:return] = { :type => rettype,
:name => 'cmock_to_return',
+7 -4
View File
@@ -68,7 +68,7 @@ CMOCK_MEM_INDEX_TYPE CMock_Guts_MemNew(CMOCK_MEM_INDEX_TYPE size)
/* determine where we're putting this new block, and init its pointer to be the end of the line */
index = CMock_Guts_FreePtr + CMOCK_MEM_INDEX_SIZE;
*(CMOCK_MEM_INDEX_TYPE*)(&CMock_Guts_Buffer[CMock_Guts_FreePtr]) = CMOCK_GUTS_NONE;
CMOCK_MEMSET(&CMock_Guts_Buffer[CMock_Guts_FreePtr], 0, CMOCK_MEM_INDEX_SIZE);
CMock_Guts_FreePtr += size;
return index;
@@ -108,7 +108,7 @@ CMOCK_MEM_INDEX_TYPE CMock_Guts_MemChain(CMOCK_MEM_INDEX_TYPE root_index, CMOCK_
next = root;
do
{
index = *(CMOCK_MEM_INDEX_TYPE*)((CMOCK_MEM_PTR_AS_INT)next - CMOCK_MEM_INDEX_SIZE);
CMOCK_MEMCPY(&index, (unsigned char*)next - CMOCK_MEM_INDEX_SIZE, sizeof(index));
if (index >= CMock_Guts_FreePtr)
{
return CMOCK_GUTS_NONE;
@@ -119,7 +119,10 @@ CMOCK_MEM_INDEX_TYPE CMock_Guts_MemChain(CMOCK_MEM_INDEX_TYPE root_index, CMOCK_
}
}
while (index > 0);
*(CMOCK_MEM_INDEX_TYPE*)((CMOCK_MEM_PTR_AS_INT)next - CMOCK_MEM_INDEX_SIZE) = (CMOCK_MEM_INDEX_TYPE)((CMOCK_MEM_PTR_AS_INT)obj - (CMOCK_MEM_PTR_AS_INT)CMock_Guts_Buffer);
{
CMOCK_MEM_INDEX_TYPE tmp = (CMOCK_MEM_INDEX_TYPE)((unsigned char*)obj - CMock_Guts_Buffer);
CMOCK_MEMCPY((unsigned char*)next - CMOCK_MEM_INDEX_SIZE, &tmp, sizeof(tmp));
}
return root_index;
}
}
@@ -141,7 +144,7 @@ CMOCK_MEM_INDEX_TYPE CMock_Guts_MemNext(CMOCK_MEM_INDEX_TYPE previous_item_index
/* if the pointer is good, then use it to look up the next index
* (we know the first element always goes in zero, so NEXT must always be > 1) */
index = *(CMOCK_MEM_INDEX_TYPE*)((CMOCK_MEM_PTR_AS_INT)previous_item - CMOCK_MEM_INDEX_SIZE);
CMOCK_MEMCPY(&index, (unsigned char*)previous_item - CMOCK_MEM_INDEX_SIZE, sizeof(index));
if ((index > 1) && (index < CMock_Guts_FreePtr))
{
return index;
+12
View File
@@ -83,6 +83,18 @@ extern const char* CMockStringMismatch;
#define CMOCK_MEM_SIZE (32768)
#endif
/* memory copy/set functions used by CMock internals and generated mocks.
* Override to use custom implementations on targets without standard libc. */
#if !defined(CMOCK_MEMCPY) || !defined(CMOCK_MEMSET)
#include <string.h>
#ifndef CMOCK_MEMCPY
#define CMOCK_MEMCPY(a, b, c) memcpy(a, b, c)
#endif
#ifndef CMOCK_MEMSET
#define CMOCK_MEMSET(a, b, c) memset(a, b, c)
#endif
#endif
/* automatically calculated defs for easier reading */
#define CMOCK_MEM_ALIGN_SIZE (CMOCK_MEM_INDEX_TYPE)(1u << CMOCK_MEM_ALIGN)
#define CMOCK_MEM_ALIGN_MASK (CMOCK_MEM_INDEX_TYPE)(CMOCK_MEM_ALIGN_SIZE - 1)
+3
View File
@@ -15,3 +15,6 @@
:treat_as_void:
- OSEK_TASK
- VOID_TYPE_CRAZINESS
:strippables:
- SAMPLE_EXTERN
- SAMPLE_MODE
+26
View File
@@ -18,6 +18,16 @@ typedef struct _POINT_T
int y;
} POINT_T;
/* The comments in the following enum are important, as are the newlines and commas */
/* This combination caused a curious bug when used together. Make sure it doesn't come back. */
typedef enum
{
MY_ERROR_ID = -18,/**< Driver not ready */
MY_OTHER_ID = -19 /**< Node-id is in LSS unconfigured
state. If objects are handled properly,
his may not be an error. */
} MY_STATE_ID_T;
/* typedef edge case;
not ANSI C but it has been done and will break cmock if not handled */
typedef void VOID_TYPE_CRAZINESS;
@@ -101,3 +111,19 @@ inline int stuff(int num)
}
return reg;
}
/* it seems like CMock didn't love a func-looking struct before a func */
#define FOO_TYPE(a) foo_##a
struct FOO_TYPE(bar) { int baz; };
char b(void);
/* Here are more macros that sorta look like functions. Only sample_func is real */
#define SAMPLE_EXTERN
#define SAMPLE_MODE
#define SAMPLE_DEPRECATED(a,b)
struct struct_a;
struct struct_b;
SAMPLE_EXTERN SAMPLE_MODE SAMPLE_DEPRECATED(1.1.2, "It was bad. real bad()")
void sample_func(struct struct_a **a,
struct struct_b **b,
...);
@@ -0,0 +1,68 @@
# =========================================================================
# CMock - Automatic Mock Generation for C
# ThrowTheSwitch.org
# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================
---
:cmock:
:plugins:
- # none
:systest:
:types: |
typedef enum {
MyTypeA,
MyTypeB,
MyTypeC,
} MyType_t;
:mockable: |
int myFunc(const MyType_t t_MyType);
:source:
:header: |
int exercise(const MyType_t t_MyType);
:code: |
int exercise(const MyType_t t_MyType)
{
return myFunc(t_MyType);
}
:tests:
:common: |
void setUp(void) {}
void tearDown(void) {}
:units:
- :pass: TRUE
:should: 'compile and pass when the correct enum value is passed and return value matches'
:code: |
test()
{
myFunc_ExpectAndReturn(MyTypeB, 42);
TEST_ASSERT_EQUAL(42, exercise(MyTypeB));
}
- :pass: FALSE
:should: 'fail when the wrong enum value is passed'
:code: |
test()
{
myFunc_ExpectAndReturn(MyTypeB, 42);
exercise(MyTypeC);
}
- :pass: TRUE
:should: 'pass when called with a const MyType_t variable'
:code: |
test()
{
const MyType_t val = MyTypeA;
myFunc_ExpectAndReturn(MyTypeA, 0);
TEST_ASSERT_EQUAL(0, exercise(val));
}
...
@@ -0,0 +1,80 @@
# =========================================================================
# CMock - Automatic Mock Generation for C
# ThrowTheSwitch.org
# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================
---
:cmock:
:plugins:
- # none
:systest:
:types: |
:mockable: |
int *const foo(float const *const self);
:source:
:header: |
int *const exercise(float const *const self);
:code: |
int *const exercise(float const *const self)
{
return foo(self);
}
:tests:
:common: |
static float f1 = 1.0f;
static float f2 = 2.0f;
static int i1 = 10;
static int i2 = 20;
static float const *const self_a = &f1;
static float const *const self_b = &f2;
static int *const ret_a = &i1;
static int *const ret_b = &i2;
void setUp(void) {}
void tearDown(void) {}
:units:
- :pass: TRUE
:should: 'compile and pass when the correct argument is passed and return value matches'
:code: |
test()
{
foo_ExpectAndReturn(self_a, ret_a);
TEST_ASSERT_EQUAL_PTR(ret_a, exercise(self_a));
}
- :pass: FALSE
:should: 'fail when the wrong argument pointer is passed'
:code: |
test()
{
foo_ExpectAndReturn(self_a, ret_a);
exercise(self_b);
}
- :pass: TRUE
:should: 'return the exact pointer provided to ExpectAndReturn'
:code: |
test()
{
foo_ExpectAndReturn(self_a, ret_b);
TEST_ASSERT_EQUAL_PTR(ret_b, exercise(self_a));
}
- :pass: FALSE
:should: 'fail when the returned pointer does not match what the test expects'
:code: |
test()
{
foo_ExpectAndReturn(self_a, ret_b);
TEST_ASSERT_EQUAL_PTR(ret_a, exercise(self_a));
}
...
@@ -0,0 +1,88 @@
# =========================================================================
# CMock - Automatic Mock Generation for C
# ThrowTheSwitch.org
# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================
---
#The purpose of this test is to verify that structs containing function pointer
#members are not mistakenly mocked by CMock. Only actual function prototypes
#at file scope should be mocked. This is especially tricky when the struct
#contains nested anonymous structs or unions, because the struct removal regex
#can be confused by the inner closing brace and leave function pointer members
#in the source for the parser to encounter.
:cmock:
:plugins:
- # none
:includes:
- "<stdint.h>"
:systest:
:types: |
#include <stdint.h>
/* Forward-declare the type so the source header can use SaladBowl* before
the full definition (which lives in the mockable header) is visible. */
typedef struct SaladBowlStruct SaladBowl;
:mockable: |
#include <stdint.h>
/* Full struct definition with a nested anonymous struct and function pointer
members. CMock must ignore all of these and only mock saladBowlInit. */
struct SaladBowlStruct {
struct {
uint16_t remainingCapacity;
uint16_t ingredientCount;
} stats;
void* (*toss)(struct SaladBowlStruct *self, uint16_t itemSize);
int32_t (*empty)(struct SaladBowlStruct *self);
void* (*grab)(struct SaladBowlStruct *self, uint16_t itemIndex);
void* (*add)(struct SaladBowlStruct *self, uint16_t itemIndex, uint16_t itemSize);
int32_t (*pluck)(struct SaladBowlStruct *self, uint16_t itemIndex);
};
int32_t saladBowlInit(SaladBowl *bowl, uint16_t sizeInBytes, uint16_t headerSizeInBytes);
void saladBowlBase(void);
void saladBowlTop(void);
/* inlines containing function calls look like function prototypes too */
inline void inlineSaladKit(void)
{
{
saladBowlBase();
}
{
saladBowlTop();
}
}
:source:
:header: |
#include <stdint.h>
void exercise_salad_bowl(SaladBowl *bowl);
:code: |
void exercise_salad_bowl(SaladBowl *bowl)
{
saladBowlInit(bowl, 256, 16);
}
:tests:
:common: |
SaladBowl g_bowl;
void setUp(void) {}
void tearDown(void) {}
:units:
- :pass: TRUE
:should: 'mock only saladBowlInit and ignore function pointer members of the struct'
:code: |
test()
{
saladBowlInit_ExpectAndReturn(&g_bowl, 256, 16, 0);
exercise_salad_bowl(&g_bowl);
}
...
@@ -0,0 +1,142 @@
# =========================================================================
# CMock - Automatic Mock Generation for C
# ThrowTheSwitch.org
# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================
---
:cmock:
:mock_path: test/mocks
:mock_prefix: mock_
:when_ptr: :smart
:plugins:
- :ignore
- :return_thru_ptr
:systest:
:types: ""
:mockable: |
void ptr_ret_int(int *r);
int ptr_ret_int_rtn(int *r);
:source:
:header: |
#include <string.h>
#define lengthof(x) (sizeof(x)/sizeof((x)[0]))
:code: |
:tests:
:common: |
void setUp(void) {}
void tearDown(void) {}
:units:
- :pass: TRUE
:should: "handle a single int* argument"
:code: |
test()
{
int r = 1;
int res = 4;
ptr_ret_int_Expect(&r);
ptr_ret_int_ReturnThruPtr_r(&res);
ptr_ret_int(&r);
TEST_ASSERT_EQUAL(4, r);
}
- :pass: TRUE
:should: "ignore a call but still return arguments"
:code: |
test()
{
int r = 1;
int res = 4;
ptr_ret_int_Ignore();
ptr_ret_int_ReturnThruPtr_r(&res);
ptr_ret_int(&r);
TEST_ASSERT_EQUAL(4, r);
}
- :pass: TRUE
:should: "queue multiple return values in ignored calls"
:code: |
test()
{
int r = 1;
int res1 = 4;
int res2 = 8;
int res3 = 16;
ptr_ret_int_Ignore();
ptr_ret_int_ReturnThruPtr_r(&res1);
ptr_ret_int_ReturnThruPtr_r(&res2);
ptr_ret_int_ReturnThruPtr_r(&res3);
ptr_ret_int(&r);
TEST_ASSERT_EQUAL(4, r);
ptr_ret_int(&r);
TEST_ASSERT_EQUAL(8, r);
ptr_ret_int(&r);
TEST_ASSERT_EQUAL(16, r);
}
- :pass: TRUE
:should: "return func and handle a single int* argument"
:code: |
test()
{
int r = 1;
int res = 4;
ptr_ret_int_rtn_ExpectAndReturn(&r,1);
ptr_ret_int_rtn_ReturnThruPtr_r(&res);
TEST_ASSERT_EQUAL_INT(1,ptr_ret_int_rtn(&r));
TEST_ASSERT_EQUAL(4, r);
}
- :pass: TRUE
:should: "ignore and return a call but still return arguments"
:code: |
test()
{
int r = 1;
int res = 4;
ptr_ret_int_rtn_IgnoreAndReturn(1);
ptr_ret_int_rtn_ReturnThruPtr_r(&res);
TEST_ASSERT_EQUAL_INT(1,ptr_ret_int_rtn(&r));
TEST_ASSERT_EQUAL(4, r);
}
- :pass: TRUE
:should: "queue multiple return values in ignore and return calls"
:code: |
test()
{
int r = 1;
int res1 = 4;
int res2 = 8;
int res3 = 16;
ptr_ret_int_rtn_IgnoreAndReturn(1);
ptr_ret_int_rtn_ReturnThruPtr_r(&res1);
ptr_ret_int_rtn_ReturnThruPtr_r(&res2);
ptr_ret_int_rtn_ReturnThruPtr_r(&res3);
TEST_ASSERT_EQUAL_INT(1,ptr_ret_int_rtn(&r));
TEST_ASSERT_EQUAL(4, r);
TEST_ASSERT_EQUAL_INT(1,ptr_ret_int_rtn(&r));
TEST_ASSERT_EQUAL(8, r);
TEST_ASSERT_EQUAL_INT(1,ptr_ret_int_rtn(&r));
TEST_ASSERT_EQUAL(16, r);
}
@@ -0,0 +1,57 @@
# =========================================================================
# CMock - Automatic Mock Generation for C
# ThrowTheSwitch.org
# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================
# Test for issue #67: mock segfault on incorrect expectations in tearDown.
# Verifies that calling a mock function in tearDown without a matching
# expectation produces a clean failure message rather than a crash.
---
:cmock:
:plugins:
- # none
:systest:
:types: |
:mockable: |
void init(void);
void deinit(void);
:source:
:header: |
/* no source functions needed for this test */
:tests:
:common: |
void setUp(void) {}
void tearDown(void)
{
/* Simulate issue #67: accidentally call a mock with no expectation.
This should produce "Called more times than expected", not a crash. */
deinit();
}
:units:
- :pass: FALSE
:should: 'fail gracefully (not segfault) when a mock is called in tearDown without any expectation'
:code: |
test()
{
/* Empty test body; tearDown calls deinit() with no expectation */
}
- :pass: FALSE
:should: 'fail gracefully when mock is called in tearDown even when test body passed'
:code: |
test()
{
/* Test body passes fine; the tearDown call is what causes failure */
init_Expect();
init();
}
...
@@ -0,0 +1,59 @@
# =========================================================================
# CMock - Automatic Mock Generation for C
# ThrowTheSwitch.org
# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================
# Test for issue #67: mock segfault on incorrect expectations in tearDown.
# Verifies that setting mock expectations in tearDown for functions that are
# never called produces a clean failure message rather than a crash.
---
:cmock:
:plugins:
- # none
:systest:
:types: |
:mockable: |
void init(void);
void deinit(void);
:source:
:header: |
/* no source functions needed for this test */
:tests:
:common: |
void setUp(void) {}
void tearDown(void)
{
/* Simulate issue #67: user meant to call deinit_Expect() but accidentally
called init_Expect() instead. The init function is never actually called,
so CMock should report "Called too few times", not segfault. */
init_Expect();
}
:units:
- :pass: FALSE
:should: 'fail gracefully (not segfault) when wrong expectations are set in tearDown'
:code: |
test()
{
/* Empty test body. tearDown sets up init_Expect() but init is never
called, so CMock_Verify should report an unmet expectation. */
}
- :pass: FALSE
:should: 'fail gracefully when wrong expectations are set in tearDown even if test body passed'
:code: |
test()
{
/* Test body is correct; tearDown's wrong expectation causes the failure */
deinit_Expect();
deinit();
}
...
+4
View File
@@ -539,6 +539,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do
" return cmock_call_instance->ReturnVal;\n",
"}\n\n"
]
@plugins.expect :run, "", [:mock_precheck_return_thru_ptr, function]
@plugins.expect :run, [" uno"], [:mock_implementation_precheck, function]
@plugins.expect :run, [" dos"," tres"], [:mock_implementation, function]
@@ -577,6 +578,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do
" return cmock_call_instance->ReturnVal;\n",
"}\n\n"
]
@plugins.expect :run, "", [:mock_precheck_return_thru_ptr, function]
@plugins.expect :run, [" uno"], [:mock_implementation_precheck, function]
@plugins.expect :run, [" dos"," tres"], [:mock_implementation, function]
@@ -618,6 +620,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do
"}\n",
"}\n\n",
]
@plugins.expect :run, "", [:mock_precheck_return_thru_ptr, function]
@plugins.expect :run, [" uno"], [:mock_implementation_precheck, function]
@plugins.expect :run, [" dos"," tres"], [:mock_implementation, function]
@@ -656,6 +659,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do
" return cmock_call_instance->ReturnVal;\n",
"}\n\n"
]
@plugins.expect :run, "", [:mock_precheck_return_thru_ptr, function]
@plugins.expect :run, [" uno"], [:mock_implementation_precheck, function]
@plugins.expect :run, [" dos"," tres"], [:mock_implementation, function]
@@ -187,4 +187,118 @@ describe CMockGeneratorPluginExpect, "Verify CMockGeneratorPluginExpect Module W
returned = @cmock_generator_plugin_expect.mock_verify(function)
assert_equal(expected, returned)
end
it "preserve const-pointer ordering in typedef struct fields for arguments" do
function = {
:name => "Willow",
:args => [
{ :name => "ptr_to_const", :type => "const int*", :ptr? => true, :const? => true, :const_ptr? => false },
{ :name => "const_ptr", :type => "int*", :ptr? => true, :const? => false, :const_ptr? => true },
{ :name => "both_const", :type => "const int*", :ptr? => true, :const? => true, :const_ptr? => true },
{ :name => "plain_ptr", :type => "int*", :ptr? => true, :const? => false, :const_ptr? => false },
],
:return => test_return[:void]
}
# Struct fields use arg[:type] directly (no reconstruction via arg_type_with_const):
# - "const int*" preserved as "const int*" (pointer to const data)
# - "int*" (from int* const) stored as "int*" — the const_ptr? is intentionally omitted
# because a const struct field can never be written to, making the mock unworkable
# - "const int*" (from const int* const) similarly stored without the trailing const
expected = " const int* Expected_ptr_to_const;\n" +
" int* Expected_const_ptr;\n" +
" const int* Expected_both_const;\n" +
" int* Expected_plain_ptr;\n"
returned = @cmock_generator_plugin_expect.instance_typedefs(function)
assert_equal(expected, returned)
end
it "preserve const-before-pointer in return typedef struct field" do
const_int_ptr_return = { :type => "const int*", :name => "cmock_to_return", :ptr? => true,
:const? => true, :const_ptr? => false, :void? => false,
:str => "const int* cmock_to_return" }
function = { :name => "Elm", :args => [], :return => const_int_ptr_return }
# ReturnVal uses return[:type] = "const int*"
expected = " const int* ReturnVal;\n"
returned = @cmock_generator_plugin_expect.instance_typedefs(function)
assert_equal(expected, returned)
end
it "preserve const on non-pointer custom type in mock function declaration but drop it from struct field" do
function = {
:name => "myFunc",
:args => [{ :name => "t_MyType", :type => "MyType_t", :ptr? => false, :const? => true, :const_ptr? => false }],
:args_string => "const MyType_t t_MyType",
:args_call => "t_MyType",
:return => test_return[:int]
}
# struct field uses arg[:type] directly — no const, so the field stays writable
expected_typedef = " int ReturnVal;\n" \
" MyType_t Expected_t_MyType;\n"
assert_equal(expected_typedef, @cmock_generator_plugin_expect.instance_typedefs(function))
# function declaration uses args_string — const MyType_t must appear in the C signature
expected_decl = "#define myFunc_Expect(t_MyType) TEST_FAIL_MESSAGE(\"myFunc requires _ExpectAndReturn\");\n" \
"#define myFunc_ExpectAndReturn(t_MyType, cmock_retval) myFunc_CMockExpectAndReturn(__LINE__, t_MyType, cmock_retval)\n" \
"void myFunc_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, const MyType_t t_MyType, int cmock_to_return);\n"
assert_equal(expected_decl, @cmock_generator_plugin_expect.mock_function_declarations(function))
end
it "store const-pointer return value without trailing const in typedef struct field (for writability)" do
int_ptr_const_return = { :type => "int*", :name => "cmock_to_return", :ptr? => true,
:const? => false, :const_ptr? => true, :void? => false,
:str => "int* const cmock_to_return" }
function = { :name => "Elm", :args => [], :return => int_ptr_const_return }
# ReturnVal uses return[:type] = "int*"; the trailing const is intentionally dropped
# so that the struct field remains assignable
expected = " int* ReturnVal;\n"
returned = @cmock_generator_plugin_expect.instance_typedefs(function)
assert_equal(expected, returned)
end
it "preserve const and pointer order in mock function declaration" do
int_ptr_const_return = { :type => "int*", :name => "cmock_to_return", :ptr? => true,
:const? => false, :const_ptr? => true, :void? => false,
:str => "int* const cmock_to_return" }
function = {
:name => "Cedar",
:args => [
{ :name => "p", :type => "const int*", :ptr? => true, :const? => true, :const_ptr? => false },
{ :name => "q", :type => "int*", :ptr? => true, :const? => false, :const_ptr? => true }
],
:args_string => "const int* p, int* const q",
:args_call => "p, q",
:return => int_ptr_const_return
}
expected = "#define Cedar_Expect(p, q) TEST_FAIL_MESSAGE(\"Cedar requires _ExpectAndReturn\");\n" +
"#define Cedar_ExpectAndReturn(p, q, cmock_retval) Cedar_CMockExpectAndReturn(__LINE__, p, q, cmock_retval)\n" +
"void Cedar_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, const int* p, int* const q, int* const cmock_to_return);\n"
returned = @cmock_generator_plugin_expect.mock_function_declarations(function)
assert_equal(expected, returned)
end
it "preserve const-before-pointer return type in mock function declaration for void-arg functions" do
const_int_ptr_return = { :type => "const int*", :name => "cmock_to_return", :ptr? => true,
:const? => true, :const_ptr? => false, :void? => false,
:str => "const int* cmock_to_return" }
function = { :name => "Oak", :args => [], :args_string => "void", :args_call => "",
:return => const_int_ptr_return }
expected = "#define Oak_Expect() TEST_FAIL_MESSAGE(\"Oak requires _ExpectAndReturn\");\n" +
"#define Oak_ExpectAndReturn(cmock_retval) Oak_CMockExpectAndReturn(__LINE__, cmock_retval)\n" +
"void Oak_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, const int* cmock_to_return);\n"
returned = @cmock_generator_plugin_expect.mock_function_declarations(function)
assert_equal(expected, returned)
end
it "preserve const-after-pointer return type in mock function declaration for void-arg functions" do
int_ptr_const_return = { :type => "int*", :name => "cmock_to_return", :ptr? => true,
:const? => false, :const_ptr? => true, :void? => false,
:str => "int* const cmock_to_return" }
function = { :name => "Oak", :args => [], :args_string => "void", :args_call => "",
:return => int_ptr_const_return }
expected = "#define Oak_Expect() TEST_FAIL_MESSAGE(\"Oak requires _ExpectAndReturn\");\n" +
"#define Oak_ExpectAndReturn(cmock_retval) Oak_CMockExpectAndReturn(__LINE__, cmock_retval)\n" +
"void Oak_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, int* const cmock_to_return);\n"
returned = @cmock_generator_plugin_expect.mock_function_declarations(function)
assert_equal(expected, returned)
end
end
@@ -57,6 +57,7 @@ describe CMockGeneratorPluginReturnThruPtr, "Verify CMockGeneratorPluginReturnTh
:contains_ptr? => true }
#no strict ordering
@config.expect :plugins, []
@cmock_generator_plugin_return_thru_ptr = CMockGeneratorPluginReturnThruPtr.new(@config, @utils)
end
@@ -192,13 +193,13 @@ describe CMockGeneratorPluginReturnThruPtr, "Verify CMockGeneratorPluginReturnTh
" if (cmock_call_instance->ReturnThruPtr_tofu_Used)\n" +
" {\n" +
" UNITY_TEST_ASSERT_NOT_NULL(tofu, cmock_line, CMockStringPtrIsNULL);\n" +
" memcpy((void*)tofu, (const void*)cmock_call_instance->ReturnThruPtr_tofu_Val,\n" +
" CMOCK_MEMCPY((void*)tofu, (const void*)cmock_call_instance->ReturnThruPtr_tofu_Val,\n" +
" cmock_call_instance->ReturnThruPtr_tofu_Size);\n" +
" }\n" +
" if (cmock_call_instance->ReturnThruPtr_bean_buffer_Used)\n" +
" {\n" +
" UNITY_TEST_ASSERT_NOT_NULL(bean_buffer, cmock_line, CMockStringPtrIsNULL);\n" +
" memcpy((void*)bean_buffer, (const void*)cmock_call_instance->ReturnThruPtr_bean_buffer_Val,\n" +
" CMOCK_MEMCPY((void*)bean_buffer, (const void*)cmock_call_instance->ReturnThruPtr_bean_buffer_Val,\n" +
" cmock_call_instance->ReturnThruPtr_bean_buffer_Size);\n" +
" }\n"
@@ -206,4 +207,74 @@ describe CMockGeneratorPluginReturnThruPtr, "Verify CMockGeneratorPluginReturnTh
assert_equal(expected, returned)
end
it "converts single pointer type to pointer-to-const via ptr_to_const" do
plugin = @cmock_generator_plugin_return_thru_ptr
assert_equal("int const*", plugin.ptr_to_const("int*"))
assert_equal("char const*", plugin.ptr_to_const("char*"))
assert_equal("uint8_t const*", plugin.ptr_to_const("uint8_t*"))
assert_equal("void const*", plugin.ptr_to_const("void*"))
assert_equal("MY_TYPE const*", plugin.ptr_to_const("MY_TYPE*"))
end
it "converts double pointer type by making inner pointer const via ptr_to_const" do
plugin = @cmock_generator_plugin_return_thru_ptr
assert_equal("char* const*", plugin.ptr_to_const("char**"))
assert_equal("int* const*", plugin.ptr_to_const("int**"))
end
it "includes int* const args (const pointer, mutable data) in typedef but excludes const int* args" do
# int* const: const_ptr?=true, const?=false → data is mutable, pointer is const
# The condition `!(arg[:const?])` checks whether the POINTED-TO data is const.
# const? is about the data, not the pointer itself, so int* const IS included.
const_ptr_func = {
:name => "Birch",
:args => [
{ :type => "int*", :name => "mutable_ptr", :ptr? => true, :const? => false, :const_ptr? => false },
{ :type => "int*", :name => "const_ptr", :ptr? => true, :const? => false, :const_ptr? => true },
{ :type => "const int*", :name => "ptr_to_const", :ptr? => true, :const? => true, :const_ptr? => false },
],
:return => test_return[:void]
}
@utils.expect :ptr_or_str?, true, ["int*"]
@utils.expect :ptr_or_str?, true, ["int*"]
@utils.expect :ptr_or_str?, true, ["const int*"]
# mutable_ptr and const_ptr are included; ptr_to_const is excluded (const?=true)
expected = " char ReturnThruPtr_mutable_ptr_Used;\n" +
" int const* ReturnThruPtr_mutable_ptr_Val;\n" +
" size_t ReturnThruPtr_mutable_ptr_Size;\n" +
" char ReturnThruPtr_const_ptr_Used;\n" +
" int const* ReturnThruPtr_const_ptr_Val;\n" +
" size_t ReturnThruPtr_const_ptr_Size;\n"
returned = @cmock_generator_plugin_return_thru_ptr.instance_typedefs(const_ptr_func)
assert_equal(expected, returned)
end
it "generates correct function signature for int* const args in mock interface" do
const_ptr_func = {
:name => "Birch",
:args => [
{ :type => "int*", :name => "const_ptr", :ptr? => true, :const? => false, :const_ptr? => true },
],
:return => test_return[:void]
}
@utils.expect :ptr_or_str?, true, ["int*"]
# ptr_to_const("int*") = "int const*", so the helper function takes int const* const_ptr
expected =
"#define Birch_ReturnThruPtr_const_ptr(const_ptr)" +
" Birch_CMockReturnMemThruPtr_const_ptr(__LINE__, const_ptr, sizeof(int))\n" +
"#define Birch_ReturnArrayThruPtr_const_ptr(const_ptr, cmock_len)" +
" Birch_CMockReturnMemThruPtr_const_ptr(__LINE__, const_ptr, (cmock_len * sizeof(*const_ptr)))\n" +
"#define Birch_ReturnMemThruPtr_const_ptr(const_ptr, cmock_size)" +
" Birch_CMockReturnMemThruPtr_const_ptr(__LINE__, const_ptr, (cmock_size))\n" +
"void Birch_CMockReturnMemThruPtr_const_ptr(UNITY_LINE_TYPE cmock_line, int const* const_ptr, size_t cmock_size);\n"
returned = @cmock_generator_plugin_return_thru_ptr.mock_function_declarations(const_ptr_func)
assert_equal(expected, returned)
end
end
+61
View File
@@ -508,4 +508,65 @@ describe CMockGeneratorUtils, "Verify CMockGeneratorUtils Module" do
" }\n"
assert_equal(expected, utils.code_verify_an_arg_expectation(function, arg))
end
it 'correctly reconstruct type strings preserving const and pointer order' do
# non-pointer: no const
assert_equal("int", CMockGeneratorUtils.arg_type_with_const({:type => "int", :ptr? => false, :const? => false, :const_ptr? => false}))
# non-pointer: const (prepended)
assert_equal("const int", CMockGeneratorUtils.arg_type_with_const({:type => "int", :ptr? => false, :const? => true, :const_ptr? => false}))
# pointer to mutable: no const
assert_equal("int*", CMockGeneratorUtils.arg_type_with_const({:type => "int*", :ptr? => true, :const? => false, :const_ptr? => false}))
# pointer to const (const int*): const already in :type, no trailing const
assert_equal("const int*", CMockGeneratorUtils.arg_type_with_const({:type => "const int*", :ptr? => true, :const? => true, :const_ptr? => false}))
# const pointer (int* const): :type has no const, const_ptr? appends " const"
assert_equal("int* const", CMockGeneratorUtils.arg_type_with_const({:type => "int*", :ptr? => true, :const? => false, :const_ptr? => true}))
# const pointer to const (const int* const)
assert_equal("const int* const", CMockGeneratorUtils.arg_type_with_const({:type => "const int*", :ptr? => true, :const? => true, :const_ptr? => true}))
# trailing-const form: int const* (same semantics as const int* but different spelling)
assert_equal("int const*", CMockGeneratorUtils.arg_type_with_const({:type => "int const*", :ptr? => true, :const? => true, :const_ptr? => false}))
# trailing-const pointer to const: int const* const
assert_equal("int const* const", CMockGeneratorUtils.arg_type_with_const({:type => "int const*", :ptr? => true, :const? => true, :const_ptr? => true}))
# custom type pointer
assert_equal("MY_TYPE*", CMockGeneratorUtils.arg_type_with_const({:type => "MY_TYPE*", :ptr? => true, :const? => false, :const_ptr? => false}))
assert_equal("const MY_TYPE", CMockGeneratorUtils.arg_type_with_const({:type => "MY_TYPE", :ptr? => false, :const? => true, :const_ptr? => false}))
# double pointer: no const_ptr, so :type used as-is
assert_equal("int**", CMockGeneratorUtils.arg_type_with_const({:type => "int**", :ptr? => true, :const? => false, :const_ptr? => false}))
assert_equal("const int**", CMockGeneratorUtils.arg_type_with_const({:type => "const int**", :ptr? => true, :const? => true, :const_ptr? => false}))
# double pointer with const_ptr: appends " const" after last *
assert_equal("int** const", CMockGeneratorUtils.arg_type_with_const({:type => "int**", :ptr? => true, :const? => false, :const_ptr? => true}))
end
it 'produce correct C declarations preserving const and pointer order' do
# const pointer to mutable int: int* const p
arg = {:type => "int*", :name => "p", :ptr? => true, :const? => false, :const_ptr? => true}
assert_equal("int* const p", CMockGeneratorUtils.arg_declaration(arg))
# pointer to const int: const int* p
arg = {:type => "const int*", :name => "p", :ptr? => true, :const? => true, :const_ptr? => false}
assert_equal("const int* p", CMockGeneratorUtils.arg_declaration(arg))
# const pointer to const int: const int* const p
arg = {:type => "const int*", :name => "p", :ptr? => true, :const? => true, :const_ptr? => true}
assert_equal("const int* const p", CMockGeneratorUtils.arg_declaration(arg))
# trailing-const form: int const* const p
arg = {:type => "int const*", :name => "p", :ptr? => true, :const? => true, :const_ptr? => true}
assert_equal("int const* const p", CMockGeneratorUtils.arg_declaration(arg))
# plain pointer: int* p
arg = {:type => "int*", :name => "p", :ptr? => true, :const? => false, :const_ptr? => false}
assert_equal("int* p", CMockGeneratorUtils.arg_declaration(arg))
# non-pointer const: const MY_TYPE v
arg = {:type => "MY_TYPE", :name => "v", :ptr? => false, :const? => true, :const_ptr? => false}
assert_equal("const MY_TYPE v", CMockGeneratorUtils.arg_declaration(arg))
end
end
+245
View File
@@ -1515,6 +1515,33 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
assert_equal(typedefs, result[:typedefs])
end
it "extract functions using a function pointer with shorthand notation and a calling convention" do
source = "void FunkyTurkey(void __stdcall * func_ptr(int arg0))"
expected = [{ :var_arg=>nil,
:return=>{ :type => "void",
:name => 'cmock_to_return',
:ptr? => false,
:const? => false,
:const_ptr? => false,
:str => "void cmock_to_return",
:void? => true
},
:name=>"FunkyTurkey",
:unscoped_name=>"FunkyTurkey",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"cmock_module_func_ptr1", :name=>"func_ptr", :ptr? => false, :string? => false, :const? => false, :const_ptr? => false}
],
:args_string=>"cmock_module_func_ptr1 func_ptr",
:args_call=>"func_ptr" }]
typedefs = ["typedef void *(__stdcall *cmock_module_func_ptr1)(int arg0);"]
result = @parser.parse("module", source)
assert_equal(expected, result[:functions])
assert_equal(typedefs, result[:typedefs])
end
it "extract functions containing a function pointer with a void" do
source = "void FunkyTurkey(void (*func_ptr)(void))"
expected = [{ :var_arg=>nil,
@@ -1845,6 +1872,59 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
assert_equal(typedefs, result[:typedefs])
end
it "extract functions containing a parenthesized pointer argument" do
source = "void func(char *str, int (* numb))"
expected = [{ :var_arg=>nil,
:return=>{ :type => "void",
:name => 'cmock_to_return',
:ptr? => false,
:const? => false,
:const_ptr? => false,
:str => "void cmock_to_return",
:void? => true
},
:name=>"func",
:unscoped_name=>"func",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args=>[ {:type=>"char*", :name=>"str", :ptr? => false, :string? => true, :const? => false, :const_ptr? => false},
{:type=>"int*", :name=>"numb", :ptr? => true, :string? => false, :const? => false, :const_ptr? => false}
],
:args_string=>"char* str, int* numb",
:args_call=>"str, numb" }]
result = @parser.parse("module", source)
assert_equal(expected, result[:functions])
assert_equal([], result[:typedefs])
end
it "extract functions containing a parenthesized const pointer argument" do
source = "void func(int (* const numb))"
expected = [{ :var_arg=>nil,
:return=>{ :type => "void",
:name => 'cmock_to_return',
:ptr? => false,
:const? => false,
:const_ptr? => false,
:str => "void cmock_to_return",
:void? => true
},
:name=>"func",
:unscoped_name=>"func",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args=>[ {:type=>"int*", :name=>"numb", :ptr? => true, :string? => false, :const? => false, :const_ptr? => true}
],
:args_string=>"int* const numb",
:args_call=>"numb" }]
result = @parser.parse("module", source)
assert_equal(expected, result[:functions])
assert_equal([], result[:typedefs])
end
it "extract functions with varargs" do
source = "int XFiles(int Scully, int Mulder, ...);\n"
expected = [{ :var_arg=>"...",
@@ -3003,4 +3083,169 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
assert_equal(true, src_len_arg[:array_size?], "src_len should be marked as array_size?")
end
it "correctly parse const int* const return type (both const qualifiers)" do
sources = [
"const int* const DoubleConst(void);\n",
"const int *const DoubleConst(void);\n",
]
# The trailing 'const' (const_ptr?) also lands in :modifier, matching the existing
# behavior seen for 'int* const' return types (see the test above for PorkRoast).
# The generated mock correctly uses return[:str] and function_return_type() which
# reconstruct the full "const int* const" from :type + const_ptr? without needing :modifier.
expected = [{ :var_arg => nil,
:name => "DoubleConst",
:unscoped_name => "DoubleConst",
:namespace => [],
:class => nil,
:return => { :type => "const int*",
:name => 'cmock_to_return',
:ptr? => true,
:const? => true,
:const_ptr? => true,
:str => "const int* const cmock_to_return",
:void? => false
},
:modifier => "const",
:contains_ptr? => false,
:args => [],
:args_string => "void",
:args_call => ""
}]
sources.each do |source|
assert_equal(expected, @parser.parse("module", source)[:functions])
end
end
it "correctly parse int const* const return type (trailing-const form, both qualifiers)" do
sources = [
"int const* const DoubleConst(void);\n",
"int const *const DoubleConst(void);\n",
]
expected = [{ :var_arg => nil,
:name => "DoubleConst",
:unscoped_name => "DoubleConst",
:namespace => [],
:class => nil,
:return => { :type => "int const*",
:name => 'cmock_to_return',
:ptr? => true,
:const? => true,
:const_ptr? => true,
:str => "int const* const cmock_to_return",
:void? => false
},
:modifier => "const",
:contains_ptr? => false,
:args => [],
:args_string => "void",
:args_call => ""
}]
sources.each do |source|
assert_equal(expected, @parser.parse("module", source)[:functions])
end
end
it "correctly parse double-pointer argument types preserving const and pointer ordering" do
# Tests the full parse pipeline for double-pointer args (not just divine_ptr_and_const).
# const int** p → const? false (const is not before the final *), type preserves "const int**"
# int** const q → const_ptr? true, type is "int**"
# int* const* p → const? true (const* before the last *), type is "int* const*"
source = "void TriplePlay(const int** a, int** const b, int* const* c);\n"
expected = [{ :var_arg => nil,
:name => "TriplePlay",
:unscoped_name => "TriplePlay",
:namespace => [],
:class => nil,
:return => { :type => "void",
:name => 'cmock_to_return',
:ptr? => false,
:const? => false,
:const_ptr? => false,
:str => "void cmock_to_return",
:void? => true
},
:modifier => "",
:contains_ptr? => true,
:args => [
{ :type => "const int**", :name => "a", :ptr? => true, :string? => false,
:const? => false, :const_ptr? => false },
{ :type => "int**", :name => "b", :ptr? => true, :string? => false,
:const? => false, :const_ptr? => true },
{ :type => "int* const*", :name => "c", :ptr? => true, :string? => false,
:const? => true, :const_ptr? => false },
],
:args_string => "const int** a, int** const b, int* const* c",
:args_call => "a, b, c"
}]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "correctly parse double-pointer argument types with all consts" do
# const int** const p → both const forms: type "const int**", const?=false, const_ptr?=true
# int* const* const q → both const forms: type "int* const*", const?=true, const_ptr?=true
source = "void AllConst(const int** const a, int* const* const b);\n"
expected = [{ :var_arg => nil,
:name => "AllConst",
:unscoped_name => "AllConst",
:namespace => [],
:class => nil,
:return => { :type => "void",
:name => 'cmock_to_return',
:ptr? => false,
:const? => false,
:const_ptr? => false,
:str => "void cmock_to_return",
:void? => true
},
:modifier => "",
:contains_ptr? => true,
:args => [
{ :type => "const int**", :name => "a", :ptr? => true, :string? => false,
:const? => false, :const_ptr? => true },
{ :type => "int* const*", :name => "b", :ptr? => true, :string? => false,
:const? => true, :const_ptr? => true },
],
:args_string => "const int** const a, int* const* const b",
:args_call => "a, b"
}]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "preserve const on non-pointer custom type arguments (e.g. const MyType_t)" do
source = "int myFunc(const MyType_t t_MyType);\n"
expected = [{ :var_arg => nil,
:name => "myFunc",
:unscoped_name => "myFunc",
:namespace => [],
:class => nil,
:return => { :type => "int",
:name => 'cmock_to_return',
:ptr? => false,
:const? => false,
:const_ptr? => false,
:str => "int cmock_to_return",
:void? => false
},
:modifier => "",
:contains_ptr? => false,
:args => [
{ :type => "MyType_t", :name => "t_MyType", :ptr? => false, :string? => false,
:const? => true, :const_ptr? => false }
],
:args_string => "const MyType_t t_MyType",
:args_call => "t_MyType"
}]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
end