Merge pull request #261 from laurensmiers/master

Inline function mocking
This commit is contained in:
Mark VanderVoord
2019-11-11 08:36:21 -05:00
committed by GitHub
12 changed files with 288 additions and 16 deletions
+8 -1
View File
@@ -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. ;)
+1
View File
@@ -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,
+7
View File
@@ -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)
+73 -14
View File
@@ -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
+1
View File
@@ -4,6 +4,7 @@
:includes: []
:mock_path: ./system/generated/
:mock_prefix: mock_
:treat_inlines: :include
:treat_as_void:
- OSEK_TASK
- VOID_TYPE_CRAZINESS
+23
View File
@@ -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
}
View File
@@ -12,6 +12,7 @@
:callback_after_arg_check: true
:callback_include_count: false
:treat_externs: :include
:treat_inlines: :include
:systest:
:types: |
+3
View File
@@ -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
+2 -1
View File
@@ -2,4 +2,5 @@
:plugins:
- 'soda'
- 'pizza'
:treat_externs: :include
:treat_externs: :include
:treat_inlines: :include
+3
View File
@@ -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"
+166
View File
@@ -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