mirror of
https://github.com/ThrowTheSwitch/CMock.git
synced 2026-08-09 04:37:50 +00:00
Merge pull request #265 from laurensmiers/master
User provided patterns for inline function mocking
This commit is contained in:
@@ -525,6 +525,37 @@ from the defaults. We've tried to specify what the defaults are below.
|
||||
* `:include` will mock inlined functions
|
||||
* `:exclude` will ignore inlined functions (default).
|
||||
|
||||
CMock will look for the following default patterns (simplified from the actual regex):
|
||||
- "static inline"
|
||||
- "inline static"
|
||||
- "inline"
|
||||
- "static"
|
||||
You can override these patterns, check out :inline_function_patterns.
|
||||
|
||||
Enabling this feature does require a change in the build system that
|
||||
is using CMock. To understand why, we need to give some more info
|
||||
on how we are handling inline functions internally.
|
||||
Let's say we want to mock a header called example.h. example.h
|
||||
contains inline functions, we cannot include this header in the
|
||||
mocks or test code if we want to mock the inline functions simply
|
||||
because the inline functions contain an implementation that we want
|
||||
to override in our mocks!
|
||||
So, to circumvent this, we generate a new header, also named
|
||||
example.h, in the same directory as mock_example.h/c . This newly
|
||||
generated header should/is exactly the same as the original header,
|
||||
only difference is the inline functions are transformed to 'normal'
|
||||
functions declarations. Placing the new header in the same
|
||||
directory as mock_example.h/c ensures that they will include the new
|
||||
header and not the old one.
|
||||
However, CMock has no control in how the build system is configured
|
||||
and which include paths the test code is compiled with. In order
|
||||
for the test code to also see the newly generated header ,and not
|
||||
the old header with inline functions, the build system has to add
|
||||
the mock folder to the include paths.
|
||||
Furthermore, we need to keep the order of include paths in mind. We
|
||||
have to set the mock folder before the other includes to avoid the
|
||||
test code including the original header instead of the newly
|
||||
generated header (without inline functions).
|
||||
|
||||
* `:unity_helper_path`:
|
||||
If you have created a header with your own extensions to unity to
|
||||
@@ -636,6 +667,21 @@ from the defaults. We've tried to specify what the defaults are below.
|
||||
If this option is disabled, the mocked functions will return
|
||||
a default value (0) when called (and only if they have to return something of course).
|
||||
|
||||
* `:inline_function_patterns`:
|
||||
An array containing a list of strings to detect inline functions.
|
||||
This option is only taken into account if you enable :treat_inlines.
|
||||
These strings are interpreted as regex patterns so be sure to escape
|
||||
certain characters. For example, use `:inline_function_patterns: ['static inline __attribute__ \(\(always_inline\)\)']`
|
||||
to recognize `static inline __attribute__ ((always_inline)) int my_func(void)`
|
||||
as an inline function.
|
||||
The default patterns are are:
|
||||
|
||||
* default: ['(static\s+inline|inline\s+static)\s*', '(\bstatic\b|\binline\b)\s*']
|
||||
* **note:**
|
||||
The order of patterns is important here!
|
||||
We go from specific patterns ('static inline') to general patterns ('inline'),
|
||||
otherwise we would miss functions that use 'static inline' iso 'inline'.
|
||||
|
||||
|
||||
Compiled Options:
|
||||
-----------------
|
||||
|
||||
@@ -40,6 +40,15 @@ class CMockConfig
|
||||
:orig_header_include_fmt => "#include \"%s\"",
|
||||
:array_size_type => [],
|
||||
:array_size_name => 'size|len',
|
||||
|
||||
# Format to look for inline functions.
|
||||
# This is a combination of "static" and "inline" keywords ("static inline", "inline static", "inline", "static")
|
||||
# There are several possibilities:
|
||||
# - sometimes they appear together, sometimes individually,
|
||||
# - The keywords can appear before or after the return type (this is a compiler warning but people do weird stuff),
|
||||
# so we check for word boundaries when searching for them
|
||||
# - We first remove "static inline" combinations and boil down to single inline or static statements
|
||||
:inline_function_patterns => ['(static\s+inline|inline\s+static)\s*', '(\bstatic\b|\binline\b)\s*'], # Last part (\s*) is just to remove whitespaces (only to prettify the output)
|
||||
}
|
||||
|
||||
def initialize(options=nil)
|
||||
|
||||
+20
-19
@@ -6,7 +6,7 @@
|
||||
|
||||
class CMockHeaderParser
|
||||
|
||||
attr_accessor :funcs, :c_attr_noconst, :c_attributes, :treat_as_void, :treat_externs, :treat_inlines
|
||||
attr_accessor :funcs, :c_attr_noconst, :c_attributes, :treat_as_void, :treat_externs, :treat_inlines, :inline_function_patterns
|
||||
|
||||
def initialize(cfg)
|
||||
@funcs = []
|
||||
@@ -25,6 +25,7 @@ class CMockHeaderParser
|
||||
@verbosity = cfg.verbosity
|
||||
@treat_externs = cfg.treat_externs
|
||||
@treat_inlines = cfg.treat_inlines
|
||||
@inline_function_patterns = cfg.inline_function_patterns
|
||||
@c_strippables += ['extern'] if (@treat_externs == :include) #we'll need to remove the attribute if we're allowing externs
|
||||
@c_strippables += ['inline'] if (@treat_inlines == :include) #we'll need to remove the attribute if we're allowing inlines
|
||||
end
|
||||
@@ -102,19 +103,16 @@ class CMockHeaderParser
|
||||
# Transform inline functions to regular functions in the source by the user
|
||||
# +source+:: String containing the source to be processed
|
||||
def transform_inline_functions(source)
|
||||
# Format to look for inline functions.
|
||||
# This is a combination of "static" and "inline" keywords ("static inline", "inline static", "inline", "static")
|
||||
# There are several possibilities:
|
||||
# - sometimes they appear together, sometimes individually,
|
||||
# - The keywords can appear before or after the return type (this is a compiler warning but people do weird stuff),
|
||||
# so we check for word boundaries when searching for them
|
||||
# - We first remove "static inline" combinations and boil down to single inline or static statements
|
||||
inline_function_regex_formats = [
|
||||
/(static\s+inline|inline\s+static)\s*/, # Last part (\s*) is just to remove whitespaces (only to prettify the output)
|
||||
/(\bstatic\b|\binline\b)\s*/, # Last part (\s*) is just to remove whitespaces (only to prettify the output)
|
||||
]
|
||||
inline_function_regex_formats = []
|
||||
square_bracket_pair_regex_format = /\{[^\{\}]*\}/ # Regex to match one whole block enclosed by two square brackets
|
||||
|
||||
# Convert user provided string patterns to regex
|
||||
@inline_function_patterns.each do |user_format_string|
|
||||
user_regex = Regexp.new(user_format_string)
|
||||
cleanup_spaces_after_user_regex = /\s*/
|
||||
inline_function_regex_formats << Regexp.new(user_regex.source + cleanup_spaces_after_user_regex.source)
|
||||
end
|
||||
|
||||
# let's clean up the encoding in case they've done anything weird with the characters we might find
|
||||
source = source.force_encoding("ISO-8859-1").encode("utf-8", :replace => nil)
|
||||
|
||||
@@ -164,6 +162,14 @@ class CMockHeaderParser
|
||||
@local_as_void += void_types.flatten.uniq.compact
|
||||
end
|
||||
|
||||
# If user wants to mock inline functions,
|
||||
# remove the (user specific) inline keywords before removing anything else to avoid missing an inline function
|
||||
if (@treat_inlines == :include)
|
||||
@inline_function_patterns.each { |user_format_string|
|
||||
source.gsub!(/#{user_format_string}/, '') # remove user defined inline function patterns
|
||||
}
|
||||
end
|
||||
|
||||
# smush multiline macros into single line (checking for continuation character at end of line '\')
|
||||
source.gsub!(/\s*\\\s*/m, ' ')
|
||||
|
||||
@@ -230,13 +236,8 @@ class CMockHeaderParser
|
||||
src_lines.delete_if {|line| !(line =~ /(?:^|\s+)(?:extern)\s+/).nil?} # remove extern functions
|
||||
end
|
||||
|
||||
if (@treat_inlines == :include)
|
||||
src_lines.each {
|
||||
|src_line|
|
||||
src_line.gsub!(/^inline/, "") # Remove "inline" so that they are 'normal' functions
|
||||
}
|
||||
else
|
||||
src_lines.delete_if {|line| !(line =~ /(?:^|\s+)(?:inline)\s+/).nil?} # remove inline functions
|
||||
unless (@treat_inlines == :include)
|
||||
src_lines.delete_if {|line| !(line =~ /(?:^|\s+)(?:inline)\s+/).nil?} # remove inline functions
|
||||
end
|
||||
|
||||
src_lines.delete_if {|line| line.empty? } #drop empty lines
|
||||
|
||||
@@ -20,6 +20,7 @@ describe CMockConfig, "Verify CMockConfig Module" do
|
||||
assert_equal(CMockConfig::CMockDefaultOptions[:plugins], config.plugins)
|
||||
assert_equal(CMockConfig::CMockDefaultOptions[:treat_externs], config.treat_externs)
|
||||
assert_equal(CMockConfig::CMockDefaultOptions[:treat_inlines], config.treat_inlines)
|
||||
assert_equal(CMockConfig::CMockDefaultOptions[:inline_function_patterns], config.inline_function_patterns)
|
||||
end
|
||||
|
||||
it "replace only options specified in a hash" do
|
||||
@@ -32,6 +33,7 @@ describe CMockConfig, "Verify CMockConfig Module" do
|
||||
assert_equal(CMockConfig::CMockDefaultOptions[:plugins], config.plugins)
|
||||
assert_equal(CMockConfig::CMockDefaultOptions[:treat_externs], config.treat_externs)
|
||||
assert_equal(CMockConfig::CMockDefaultOptions[:treat_inlines], config.treat_inlines)
|
||||
assert_equal(CMockConfig::CMockDefaultOptions[:inline_function_patterns], config.inline_function_patterns)
|
||||
end
|
||||
|
||||
it "replace only options specified in a yaml file" do
|
||||
@@ -43,6 +45,7 @@ describe CMockConfig, "Verify CMockConfig Module" do
|
||||
assert_equal(test_plugins, config.plugins)
|
||||
assert_equal(:include, config.treat_externs)
|
||||
assert_equal(:include, config.treat_inlines)
|
||||
assert_equal(['MY_INLINE_FUNCTION_DECLARATION_PATTERN'], config.inline_function_patterns)
|
||||
end
|
||||
|
||||
it "populate treat_as map with internal standard_treat_as_map defaults, redefine defaults, and add custom values" do
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
- 'pizza'
|
||||
:treat_externs: :include
|
||||
:treat_inlines: :include
|
||||
:inline_function_patterns: ['MY_INLINE_FUNCTION_DECLARATION_PATTERN']
|
||||
|
||||
@@ -24,6 +24,7 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
|
||||
@config.expect :verbosity, 1
|
||||
@config.expect :treat_externs, :exclude
|
||||
@config.expect :treat_inlines, :exclude
|
||||
@config.expect :inline_function_patterns, ['(static\s+inline|inline\s+static)\s*', '(\bstatic\b|\binline\b)\s*']
|
||||
@config.expect :array_size_type, ['int', 'size_t']
|
||||
@config.expect :array_size_name, 'size|len'
|
||||
|
||||
@@ -509,6 +510,37 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
|
||||
assert_equal(expected, @parser.import_source(source).map!{|s|s.strip})
|
||||
end
|
||||
|
||||
it "Include inline functions that contain user defined inline function formats" do
|
||||
source =
|
||||
"uint32 foo(unsigned int);\n" +
|
||||
"uint32 bar(unsigned int);\n" +
|
||||
"inline void inlineBar(void)\n" +
|
||||
"{\n" +
|
||||
" return 43;\n" +
|
||||
"}\n" +
|
||||
"static __inline__ __attribute__ ((always_inline)) int alwaysinlinefunc(int a)\n" +
|
||||
"{\n" +
|
||||
" return a + inlineBar();\n" +
|
||||
"}\n" +
|
||||
"static __inline__ void inlinebar(unsigned int)\n" +
|
||||
"{\n" +
|
||||
" int a = alwaysinlinefunc()\n" +
|
||||
"}\n"
|
||||
|
||||
expected =
|
||||
[
|
||||
"uint32 foo(unsigned int)",
|
||||
"uint32 bar(unsigned int)",
|
||||
"void inlineBar(void)",
|
||||
"int alwaysinlinefunc(int a)",
|
||||
"void inlinebar(unsigned int)"
|
||||
]
|
||||
|
||||
@parser.treat_inlines = :include
|
||||
@parser.inline_function_patterns = ['static __inline__ __attribute__ \(\(always_inline\)\)', 'static __inline__', '\binline\b']
|
||||
assert_equal(expected, @parser.import_source(source).map!{|s|s.strip})
|
||||
end
|
||||
|
||||
it "remove defines" do
|
||||
source =
|
||||
"#define whatever you feel like defining\n" +
|
||||
|
||||
Reference in New Issue
Block a user