diff --git a/docs/CMock_Summary.md b/docs/CMock_Summary.md index afbcc10..df0ca5d 100644 --- a/docs/CMock_Summary.md +++ b/docs/CMock_Summary.md @@ -518,6 +518,14 @@ from the defaults. We've tried to specify what the defaults are below. * `:include` will mock externed functions * `:exclude` will ignore externed functions (default). +* `:treat_inlines`: + This specifies how you want CMock to handle functions that have been + marked as inline in the header file. Should it mock them? + + * `:include` will mock inlined functions + * `:exclude` will ignore inlined functions (default). + + * `:unity_helper_path`: If you have created a header with your own extensions to unity to handle your own types, you can set this argument to that path. CMock @@ -686,4 +694,3 @@ you might tool CMock into your build process. You may also want to consider using [Ceedling](https://throwtheswitch.org/ceedling). Please note that these examples are meant to show how the build process works. They have failing tests ON PURPOSE to show what that would look like. Don't be alarmed. ;) - diff --git a/lib/cmock_config.rb b/lib/cmock_config.rb index b21b61e..caeeac1 100644 --- a/lib/cmock_config.rb +++ b/lib/cmock_config.rb @@ -29,6 +29,7 @@ class CMockConfig :when_ptr => :compare_data, #the options being :compare_ptr, :compare_data, or :smart :verbosity => 2, #the options being 0 errors only, 1 warnings and errors, 2 normal info, 3 verbose :treat_externs => :exclude, #the options being :include or :exclude + :treat_inlines => :exclude, #the options being :include or :exclude :callback_include_count => true, :callback_after_arg_check => false, :includes => nil, diff --git a/lib/cmock_generator.rb b/lib/cmock_generator.rb index 42725a6..85ee77f 100644 --- a/lib/cmock_generator.rb +++ b/lib/cmock_generator.rb @@ -16,6 +16,7 @@ class CMockGenerator @prefix = @config.mock_prefix @suffix = @config.mock_suffix @weak = @config.weak + @include_inline = @config.treat_inlines @ordered = @config.enforce_strict_ordering @framework = @config.framework.to_s @fail_on_unexpected_calls = @config.fail_on_unexpected_calls @@ -61,6 +62,12 @@ class CMockGenerator end def create_mock_header_file(parsed_stuff) + if @include_inline == :include + @file_writer.create_file(@module_name + ".h", @subdir) do |file, filename| + file << parsed_stuff[:normalized_source] + end + end + @file_writer.create_file(@mock_name + ".h", @subdir) do |file, filename| create_mock_header_header(file, filename) create_mock_header_service_call_declarations(file) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 0cf1947..cc6bcfa 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 + attr_accessor :funcs, :c_attr_noconst, :c_attributes, :treat_as_void, :treat_externs, :treat_inlines def initialize(cfg) @funcs = [] @@ -24,13 +24,16 @@ class CMockHeaderParser @local_as_void = @treat_as_void @verbosity = cfg.verbosity @treat_externs = cfg.treat_externs + @treat_inlines = cfg.treat_inlines @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 def parse(name, source) @module_name = name.gsub(/\W/,'') @typedefs = [] @funcs = [] + @normalized_source = nil function_names = [] parse_functions( import_source(source) ).map do |decl| @@ -41,14 +44,62 @@ class CMockHeaderParser end end + @normalized_source = if (@treat_inlines == :include) + transform_inline_functions(source) + else + '' + end + { :includes => nil, :functions => @funcs, - :typedefs => @typedefs + :typedefs => @typedefs, + :normalized_source => @normalized_source } end private if $ThisIsOnlyATest.nil? ################ + def remove_nested_pairs_of_braces(source) + # remove nested pairs of braces because no function declarations will be inside of them (leave outer pair for function definition detection) + if (RUBY_VERSION.split('.')[0].to_i > 1) + #we assign a string first because (no joke) if Ruby 1.9.3 sees this line as a regex, it will crash. + r = "\\{([^\\{\\}]*|\\g<0>)*\\}" + source.gsub!(/#{r}/m, '{ }') + else + while source.gsub!(/\{[^\{\}]*\{[^\{\}]*\}[^\{\}]*\}/m, '{ }') + end + end + + return source + end + + def transform_inline_functions(source) + # 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) + + source.gsub!(/(static|inline)+.*\{.*\w*\}/m) do |m| + m.gsub!(/(static|inline)/, '') # remove static and inline keywords + m = remove_nested_pairs_of_braces(m) + + # Functions having "{ }" at this point are/were inline functions, + # Disguise them as normal functions with the ";" + m.gsub!(/\s*\{\s\}/, ";") + + # Cleanup the function declarations + # Not strictly necessary, it will compile just fine, but it can help during debugging + m_lines = m.split(/\s*;\s*/).uniq + m_lines.each do |m_line| + m_line.gsub!(/^\s+/, '') # remove extra white space from beginning of line + m_line.gsub!(/\s+/, ' ') # remove remaining extra white space + m_line.gsub!(/\n/, '') # remove newlines + end + + m_lines.join(";\n") + ";" # Join the lines and add the last semicolon manually + end + + return source + end + def import_source(source) # let's clean up the encoding in case they've done anything weird with the characters we might find @@ -100,16 +151,15 @@ class CMockHeaderParser "#{functype} #{$2.strip}(#{$3});" end - # remove nested pairs of braces because no function declarations will be inside of them (leave outer pair for function definition detection) - if (RUBY_VERSION.split('.')[0].to_i > 1) - #we assign a string first because (no joke) if Ruby 1.9.3 sees this line as a regex, it will crash. - r = "\\{([^\\{\\}]*|\\g<0>)*\\}" - source.gsub!(/#{r}/m, '{ }') - else - while source.gsub!(/\{[^\{\}]*\{[^\{\}]*\}[^\{\}]*\}/m, '{ }') - end + source = remove_nested_pairs_of_braces(source) + + if (@treat_inlines == :include) + # Functions having "{ }" at this point are/were inline functions, + # User wants them in so 'disguise' them as normal functions with the ";" + source.gsub!("{ }", ";") end + # remove function definitions by stripping off the arguments right now source.gsub!(/\([^\)]*\)\s*\{[^\}]*\}/m, ";") @@ -124,11 +174,20 @@ class CMockHeaderParser src_lines = source.split(/\s*;\s*/).uniq src_lines.delete_if {|line| line.strip.length == 0} # remove blank lines src_lines.delete_if {|line| !(line =~ /[\w\s\*]+\(+\s*\*[\*\s]*[\w\s]+(?:\[[\w\s]*\]\s*)+\)+\s*\((?:[\w\s\*]*,?)*\s*\)/).nil?} #remove function pointer arrays - if (@treat_externs == :include) - src_lines.delete_if {|line| !(line =~ /(?:^|\s+)(?:inline)\s+/).nil?} # remove inline functions - else - src_lines.delete_if {|line| !(line =~ /(?:^|\s+)(?:extern|inline)\s+/).nil?} # remove inline and extern functions + + unless (@treat_externs == :include) + 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 + end + src_lines.delete_if {|line| line.empty? } #drop empty lines end diff --git a/test/system/test_compilation/config.yml b/test/system/test_compilation/config.yml index d87c578..787e2e1 100644 --- a/test/system/test_compilation/config.yml +++ b/test/system/test_compilation/config.yml @@ -4,6 +4,7 @@ :includes: [] :mock_path: ./system/generated/ :mock_prefix: mock_ + :treat_inlines: :include :treat_as_void: - OSEK_TASK - VOID_TYPE_CRAZINESS diff --git a/test/system/test_compilation/inline.h b/test/system/test_compilation/inline.h new file mode 100644 index 0000000..ae026e0 --- /dev/null +++ b/test/system/test_compilation/inline.h @@ -0,0 +1,23 @@ + +static inline void dummy_func_0(void) { + return 5; +} + +inline static void dummy_func_1(int a) { + int a = dummy_func_0(); + int b = 10; + + return a + b; +} + +void inline static dummy_func_2(int a, char b, float c) { + c += 3.14; + b -= 32; + return a + (int)(b) + c; +} + +void dummy_normal_func(int a); + +inline void dummy_func_3(void) { + //NOP +} diff --git a/test/system/test_compilation/osek.h b/test/system/test_compilation/osek.h old mode 100755 new mode 100644 diff --git a/test/system/test_interactions/all_plugins_coexist.yml b/test/system/test_interactions/all_plugins_coexist.yml index ad73661..f1c4e38 100644 --- a/test/system/test_interactions/all_plugins_coexist.yml +++ b/test/system/test_interactions/all_plugins_coexist.yml @@ -12,6 +12,7 @@ :callback_after_arg_check: true :callback_include_count: false :treat_externs: :include + :treat_inlines: :include :systest: :types: | diff --git a/test/unit/cmock_config_test.rb b/test/unit/cmock_config_test.rb index 291c2cc..b8728c9 100644 --- a/test/unit/cmock_config_test.rb +++ b/test/unit/cmock_config_test.rb @@ -19,6 +19,7 @@ describe CMockConfig, "Verify CMockConfig Module" do assert_equal(CMockConfig::CMockDefaultOptions[:attributes], config.attributes) 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) end it "replace only options specified in a hash" do @@ -30,6 +31,7 @@ describe CMockConfig, "Verify CMockConfig Module" do assert_equal(test_attributes, config.attributes) 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) end it "replace only options specified in a yaml file" do @@ -40,6 +42,7 @@ describe CMockConfig, "Verify CMockConfig Module" do assert_nil(config.includes) assert_equal(test_plugins, config.plugins) assert_equal(:include, config.treat_externs) + assert_equal(:include, config.treat_inlines) 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 b2444f8..61204f6 100644 --- a/test/unit/cmock_config_test.yml +++ b/test/unit/cmock_config_test.yml @@ -2,4 +2,5 @@ :plugins: - 'soda' - 'pizza' - :treat_externs: :include + :treat_externs: :include + :treat_inlines: :include diff --git a/test/unit/cmock_generator_main_test.rb b/test/unit/cmock_generator_main_test.rb index 548ddb9..b7bc113 100644 --- a/test/unit/cmock_generator_main_test.rb +++ b/test/unit/cmock_generator_main_test.rb @@ -54,6 +54,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do @config.expect :includes_c_post_header, nil @config.expect :subdir, nil @config.expect :fail_on_unexpected_calls, true + @config.expect :treat_inlines, :exclude @cmock_generator = CMockGenerator.new(@config, @file_writer, @utils, @plugins) @cmock_generator.module_name = @module_name @cmock_generator.mock_name = "Mock#{@module_name}" @@ -72,6 +73,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do @config.expect :includes_c_post_header, nil @config.expect :subdir, nil @config.expect :fail_on_unexpected_calls, true + @config.expect :treat_inlines, :exclude @cmock_generator_strict = CMockGenerator.new(@config, @file_writer, @utils, @plugins) @cmock_generator_strict.module_name = @module_name @cmock_generator_strict.mock_name = "Mock#{@module_name}" @@ -133,6 +135,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do @config.expect :includes_c_post_header, nil @config.expect :subdir, nil @config.expect :fail_on_unexpected_calls, true + @config.expect :treat_inlines, :exclude @cmock_generator2 = CMockGenerator.new(@config, @file_writer, @utils, @plugins) @cmock_generator2.module_name = "Pout-Pout Fish" @cmock_generator2.mock_name = "MockPout-Pout Fish" diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index fa156f4..b2cd653 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -23,6 +23,7 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do @config.expect :when_no_prototypes, :error @config.expect :verbosity, 1 @config.expect :treat_externs, :exclude + @config.expect :treat_inlines, :exclude @config.expect :array_size_type, ['int', 'size_t'] @config.expect :array_size_name, 'size|len' @@ -453,6 +454,60 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(expected, @parser.import_source(source).map!{|s|s.strip}) end + it "leave inline functions if inline to be included" do + source = + "extern uint32 foobar(unsigned int);\n" + + "uint32 extern_name_func(unsigned int);\n" + + "uint32 funcinline(unsigned int);\n" + + "inline void inlineBar(unsigned int);\n" + + "extern int extern_bar(void);\n" + + "static inline void staticinlineBar(unsigned int);\n" + + "static inline void bar(unsigned int);\n" + + "static inline void bar(unsigned int)\n" + + "{\n" + + " // NOP\n" + + "}\n" + + expected = + [ "uint32 extern_name_func(unsigned int)", + "uint32 funcinline(unsigned int)", + "void inlineBar(unsigned int)", + "void staticinlineBar(unsigned int)", + "void bar(unsigned int)" + ] + + @parser.treat_inlines = :include + assert_equal(expected, @parser.import_source(source).map!{|s|s.strip}) + end + + it "leave inline and extern functions if inline and extern to be included" do + source = + "extern uint32 foobar(unsigned int);\n" + + "uint32 extern_name_func(unsigned int);\n" + + "uint32 funcinline(unsigned int);\n" + + "inline void inlineBar(unsigned int);\n" + + "extern int extern_bar(void);\n" + + "static inline void staticinlineBar(unsigned int);\n" + + "static inline void bar(unsigned int);\n" + + "static inline void bar(unsigned int)\n" + + "{\n" + + " // NOP\n" + + "}\n" + + expected = + [ "extern uint32 foobar(unsigned int)", + "uint32 extern_name_func(unsigned int)", + "uint32 funcinline(unsigned int)", + "void inlineBar(unsigned int)", + "extern int extern_bar(void)", + "void staticinlineBar(unsigned int)", + "void bar(unsigned int)" + ] + + @parser.treat_externs = :include + @parser.treat_inlines = :include + assert_equal(expected, @parser.import_source(source).map!{|s|s.strip}) + end it "remove defines" do source = @@ -1725,4 +1780,115 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do end end + it "Transform inline functions doesn't change a header with no inlines" do + source = + "#ifndef _NOINCLUDES\n" + + "#define _NOINCLUDES\n" + + "#include \"unity.h\"\n" + + "#include \"cmock.h\"\n" + + "#include \"YetAnotherHeader.h\"\n" + + "\n" + + "/* Ignore the following warnings since we are copying code */\n" + + "#if defined(__GNUC__) && !defined(__ICC) && !defined(__TMS470__)\n" + + "#if __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ > 6 || (__GNUC_MINOR__ == 6 && __GNUC_PATCHLEVEL__ > 0)))\n" + + "#pragma GCC diagnostic push\n" + + "#endif\n" + + "#if !defined(__clang__)\n" + + "#pragma GCC diagnostic ignored \"-Wpragmas\"\n" + + "#endif\n" + + "#pragma GCC diagnostic ignored \"-Wunknown-pragmas\"\n" + + "#pragma GCC diagnostic ignored \"-Wduplicate-decl-specifier\"\n" + + "#endif\n" + + "\n" + + "struct my_struct {\n" + + "int a;\n" + + "int b;\n" + + "int b;\n" + + "char c;\n" + + "};\n" + + "int my_function(int a);\n" + + "int my_better_function(struct my_struct *s);\n" + + "\n" + + "#endif _NOINCLUDES\n" + + assert_equal(source, @parser.transform_inline_functions(source)) + end + + it "Transform inline functions changes inline functions to function declarations" do + source = + "#ifndef _NOINCLUDES\n" + + "#define _NOINCLUDES\n" + + "#include \"unity.h\"\n" + + "#include \"cmock.h\"\n" + + "#include \"YetAnotherHeader.h\"\n" + + "\n" + + "/* Ignore the following warnings since we are copying code */\n" + + "#if defined(__GNUC__) && !defined(__ICC) && !defined(__TMS470__)\n" + + "#if __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ > 6 || (__GNUC_MINOR__ == 6 && __GNUC_PATCHLEVEL__ > 0)))\n" + + "#pragma GCC diagnostic push\n" + + "#endif\n" + + "#if !defined(__clang__)\n" + + "#pragma GCC diagnostic ignored \"-Wpragmas\"\n" + + "#endif\n" + + "#pragma GCC diagnostic ignored \"-Wunknown-pragmas\"\n" + + "#pragma GCC diagnostic ignored \"-Wduplicate-decl-specifier\"\n" + + "#endif\n" + + "\n" + + "struct my_struct {\n" + + "int a;\n" + + "int b;\n" + + "int b;\n" + + "char c;\n" + + "};\n" + + "int my_function(int a);\n" + + "int my_better_function(struct my_struct *s);\n" + + "static inline int get_member_a(struct my_struct *s)\n" + + "{\n" + + " return s->a;\n" + + "}\n" + + "inline static int my_func_0(int a)\n" + + "{\n" + + " return a + 42;\n" + + "}\n" + + "inline int my_func_1(struct my_struct *s)\n" + + "{\n" + + " return get_member_a(s) + 42;\n" + + "}\n" + + "#endif _NOINCLUDES\n" + + expected = + "#ifndef _NOINCLUDES\n" + + "#define _NOINCLUDES\n" + + "#include \"unity.h\"\n" + + "#include \"cmock.h\"\n" + + "#include \"YetAnotherHeader.h\"\n" + + "\n" + + "/* Ignore the following warnings since we are copying code */\n" + + "#if defined(__GNUC__) && !defined(__ICC) && !defined(__TMS470__)\n" + + "#if __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ > 6 || (__GNUC_MINOR__ == 6 && __GNUC_PATCHLEVEL__ > 0)))\n" + + "#pragma GCC diagnostic push\n" + + "#endif\n" + + "#if !defined(__clang__)\n" + + "#pragma GCC diagnostic ignored \"-Wpragmas\"\n" + + "#endif\n" + + "#pragma GCC diagnostic ignored \"-Wunknown-pragmas\"\n" + + "#pragma GCC diagnostic ignored \"-Wduplicate-decl-specifier\"\n" + + "#endif\n" + + "\n" + + "struct my_struct {\n" + + "int a;\n" + + "int b;\n" + + "int b;\n" + + "char c;\n" + + "};\n" + + "int my_function(int a);\n" + + "int my_better_function(struct my_struct *s);\n" + + "int get_member_a(struct my_struct *s);\n" + + "int my_func_0(int a);\n" + + "int my_func_1(struct my_struct *s);\n" + + "#endif _NOINCLUDES\n" + + assert_equal(expected, @parser.transform_inline_functions(source)) + end + end