Merge pull request #265 from laurensmiers/master

User provided patterns for inline function mocking
This commit is contained in:
Mark VanderVoord
2019-11-15 11:56:32 -05:00
committed by GitHub
6 changed files with 111 additions and 19 deletions
+46
View File
@@ -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:
-----------------
+9
View File
@@ -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
View File
@@ -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
+3
View File
@@ -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
+1
View File
@@ -4,3 +4,4 @@
- 'pizza'
:treat_externs: :include
:treat_inlines: :include
:inline_function_patterns: ['MY_INLINE_FUNCTION_DECLARATION_PATTERN']
+32
View File
@@ -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" +