diff --git a/docs/CMock_Summary.md b/docs/CMock_Summary.md index df0ca5d..79351c1 100644 --- a/docs/CMock_Summary.md +++ b/docs/CMock_Summary.md @@ -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: ----------------- diff --git a/lib/cmock_config.rb b/lib/cmock_config.rb index 6f47eb4..0ec13c4 100644 --- a/lib/cmock_config.rb +++ b/lib/cmock_config.rb @@ -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) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index d47b539..2e2e100 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -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 diff --git a/test/unit/cmock_config_test.rb b/test/unit/cmock_config_test.rb index b8728c9..aa081aa 100644 --- a/test/unit/cmock_config_test.rb +++ b/test/unit/cmock_config_test.rb @@ -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 diff --git a/test/unit/cmock_config_test.yml b/test/unit/cmock_config_test.yml index 61204f6..eaee14b 100644 --- a/test/unit/cmock_config_test.yml +++ b/test/unit/cmock_config_test.yml @@ -4,3 +4,4 @@ - 'pizza' :treat_externs: :include :treat_inlines: :include + :inline_function_patterns: ['MY_INLINE_FUNCTION_DECLARATION_PATTERN'] diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index 07b77ee..c28ebec 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -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" +