add support for mocking C++ static class member methods

This commit is contained in:
Tuc-An
2020-04-06 11:20:15 -04:00
parent b32e3cd601
commit e06540f3d7
5 changed files with 788 additions and 14 deletions
+36
View File
@@ -748,6 +748,42 @@ themselves. Call it during a test to have CMock validate everything to this poin
and start over clean. This is really useful when wanting to test a function in
an iterative manner with different arguments.
C++ Support
---------
C++ unit test/mocking frameworks often use a completely different approach (vs.
CMock) that relies on overloading virtual class members and does not support
directly mocking static class member methods or free functions (i.e., functions
in plain C). One workaround is to wrap the non-virtual functions in an object
that exposes them as virtual methods and modify your code to inject mocks at
run-time... but there is another way!
Simply use CMock to mock the static member methods and a C++ mocking framework
to handle the virtual methods. (Yes, you can mix mocks from CMock and a C++
mocking framework together in the same test!)
Keep in mind that since C++ mocking frameworks often link the real object to the
unit test too, we need to resolve multiple definition errors with something like
the following in the source of the real implementation for any functions that
CMock mocks:
#if defined(TEST)
__attribute__((weak))
#endif
To address potential issues with re-using the same function name in different
namespaces/classes, the generated function names include the namespace(s) and
class. For example:
namespace MyNamespace {
class MyClass {
static int DoesSomething(int a, int b);
};
}
Will generate functions like
void MyNamespace_MyClass_DoesSomething_ExpectAndReturn(int a, int b, int toReturn);
Examples
========
+27 -4
View File
@@ -65,6 +65,10 @@ class CMockGenerator
@file_writer.create_subdir(@subdir)
end
def create_using_statement(file, function)
file << "using namespace #{function[:namespace].join('::')};\n" unless function[:namespace].empty?
end
def create_mock_header_file(parsed_stuff)
if @include_inline == :include
@file_writer.create_file(@module_name + (@module_ext || '.h'), @subdir) do |file, _filename|
@@ -77,6 +81,7 @@ class CMockGenerator
create_mock_header_service_call_declarations(file)
create_typedefs(file, parsed_stuff[:typedefs])
parsed_stuff[:functions].each do |function|
create_using_statement(file, function)
file << @plugins.run(:mock_function_declarations, function)
end
create_mock_header_footer(file)
@@ -253,15 +258,26 @@ class CMockGenerator
args_string = function[:args_string]
args_string += (', ' + function[:var_arg]) unless function[:var_arg].nil?
# Encapsulate in namespace(s) if applicable
function[:namespace].each do |ns|
file << "namespace #{ns} {\n"
end
# Determine class prefix (if any)
cls_pre = ''
unless function[:class].nil?
cls_pre = "#{function[:class]}::"
end
# Create mock function
unless @weak.empty?
file << "#if defined (__IAR_SYSTEMS_ICC__)\n"
file << "#pragma weak #{function[:name]}\n"
file << "#pragma weak #{function[:unscoped_name]}\n"
file << "#else\n"
file << "#{function_mod_and_rettype} #{function[:name]}(#{args_string}) #{weak};\n"
file << "#{function_mod_and_rettype} #{function[:unscoped_name]}(#{args_string}) #{weak};\n"
file << "#endif\n\n"
end
file << "#{function_mod_and_rettype} #{function[:name]}(#{args_string})\n"
file << "#{function_mod_and_rettype} #{cls_pre}#{function[:unscoped_name]}(#{args_string})\n"
file << "{\n"
file << " UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;\n"
file << " CMOCK_#{function[:name]}_CALL_INSTANCE* cmock_call_instance;\n"
@@ -280,7 +296,14 @@ class CMockGenerator
file << @plugins.run(:mock_implementation, function)
file << " UNITY_CLR_DETAILS();\n"
file << " return cmock_call_instance->ReturnVal;\n" unless function[:return][:void?]
file << "}\n\n"
file << "}\n"
# Close any namespace(s) opened above
function[:namespace].each do
file << "}\n"
end
file << "\n"
end
def create_mock_interfaces(file, function)
+81 -10
View File
@@ -37,8 +37,10 @@ class CMockHeaderParser
@normalized_source = nil
function_names = []
parse_functions(import_source(source)).map do |decl|
func = parse_declaration(decl)
all_funcs = parse_functions(import_source(source)).map { |item| [item] }
all_funcs += parse_cpp_functions(import_source(source, true))
all_funcs.map do |decl|
func = parse_declaration(*decl)
unless function_names.include? func[:name]
@funcs << func
function_names << func[:name]
@@ -180,7 +182,7 @@ class CMockHeaderParser
source
end
def import_source(source)
def import_source(source, cpp = false)
# 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)
@@ -222,7 +224,10 @@ class CMockHeaderParser
# forward declared structs are removed before struct definitions so they don't mess up real thing later. we leave structs keywords in function prototypes
source.gsub!(/^[\w\s]*struct[^;\{\}\(\)]+;/m, '') # remove forward declared structs
source.gsub!(/^[\w\s]*(enum|union|struct|typedef)[\w\s]*\{[^\}]+\}[\w\s\*\,]*;/m, '') # remove struct, union, and enum definitions and typedefs with braces
source.gsub!(/(\W)(?:register|auto|static|restrict)(\W)/, '\1\2') # remove problem keywords
# remove problem keywords
source.gsub!(/(\W)(?:register|auto|restrict)(\W)/, '\1\2')
source.gsub!(/(\W)(?:static)(\W)/, '\1\2') unless cpp
source.gsub!(/\s*=\s*['"a-zA-Z0-9_\.]+\s*/, '') # remove default value statements from argument lists
source.gsub!(/^(?:[\w\s]*\W)?typedef\W[^;]*/m, '') # remove typedef statements
source.gsub!(/\)(\w)/, ') \1') # add space between parenthese and alphanumeric
@@ -234,11 +239,13 @@ class CMockHeaderParser
# scan for functions which return function pointers, because they are a pain
source.gsub!(/([\w\s\*]+)\(*\(\s*\*([\w\s\*]+)\s*\(([\w\s\*,]*)\)\)\s*\(([\w\s\*,]*)\)\)*/) do |_m|
functype = "cmock_#{@module_name}_func_ptr#{@typedefs.size + 1}"
@typedefs << "typedef #{Regexp.last_match(1).strip}(*#{functype})(#{Regexp.last_match(4)});"
"#{functype} #{Regexp.last_match(2).strip}(#{Regexp.last_match(3)});"
unless cpp # only collect once
@typedefs << "typedef #{Regexp.last_match(1).strip}(*#{functype})(#{Regexp.last_match(4)});"
"#{functype} #{Regexp.last_match(2).strip}(#{Regexp.last_match(3)});"
end
end
source = remove_nested_pairs_of_braces(source)
source = remove_nested_pairs_of_braces(source) unless cpp
if @treat_inlines == :include
# Functions having "{ }" at this point are/were inline functions,
@@ -257,7 +264,8 @@ class CMockHeaderParser
source.gsub!(/\s+/, ' ') # remove remaining extra white space
# split lines on semicolons and remove things that are obviously not what we are looking for
src_lines = source.split(/\s*;\s*/).uniq
src_lines = source.split(/\s*;\s*/)
src_lines = src_lines.uniq unless cpp # must retain closing braces for class/namespace
src_lines.delete_if { |line| line.strip.empty? } # 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
@@ -272,6 +280,55 @@ class CMockHeaderParser
src_lines.delete_if(&:empty?) # drop empty lines
end
# Rudimentary C++ parser - does not handle all situations - e.g.:
# * A namespace function appears after a class with private members (should be parsed)
# * Anonymous namespace (shouldn't parse anything - no matter how nested - within it)
# * A class nested within another class
def parse_cpp_functions(source)
funcs = []
ns = []
pub = false
source.each do |line|
# Search for namespace, class, opening and closing braces
line.scan(/(?:(?:\b(?:namespace|class)\s+(?:\S+)\s*)?{)|}/).each do |item|
if item == '}'
ns.pop
else
token = item.strip.sub(/\s+/, ' ')
ns << token
pub = false if token.start_with? 'class'
pub = true if token.start_with? 'namespace'
end
end
pub = true if line =~ /public:/
pub = false if line =~ /private:/ || line =~ /protected:/
# ignore non-public and non-static
next unless pub
next unless line =~ /\bstatic\b/
line.sub!(/^.*static/, '')
next unless line =~ @declaration_parse_matcher
tmp = ns.reject { |item| item == '{' }
# Identify class name, if any
cls = nil
if tmp[-1].start_with? 'class '
cls = tmp.pop.sub(/class (\S+) {/, '\1')
end
# Assemble list of namespaces
tmp.each { |item| item.sub!(/(?:namespace|class) (\S+) {/, '\1') }
funcs << [line.strip.gsub(/\s+/, ' '), tmp, cls]
end
funcs
end
def parse_functions(source)
funcs = []
source.each { |line| funcs << line.strip.gsub(/\s+/, ' ') if line =~ @declaration_parse_matcher }
@@ -442,8 +499,10 @@ class CMockHeaderParser
end
end
def parse_declaration(declaration)
def parse_declaration(declaration, namespace = [], classname = nil)
decl = {}
decl[:namespace] = namespace
decl[:class] = classname
regex_match = @declaration_parse_matcher.match(declaration)
raise "Failed parsing function declaration: '#{declaration}'" if regex_match.nil?
@@ -454,7 +513,19 @@ class CMockHeaderParser
# process function attributes, return type, and name
parsed = parse_type_and_name(regex_match[1])
decl[:name] = parsed[:name]
# Record original name without scope prefix
decl[:unscoped_name] = parsed[:name]
# Prefix name with namespace scope (if any) and then class
decl[:name] = namespace.join('_')
unless classname.nil?
decl[:name] << '_' unless decl[:name].empty?
decl[:name] << classname
end
# Add original name to complete fully scoped name
decl[:name] << '_' unless decl[:name].empty?
decl[:name] << decl[:unscoped_name]
decl[:modifier] = parsed[:modifier]
unless parsed[:c_calling_convention].nil?
decl[:c_calling_convention] = parsed[:c_calling_convention]
+125
View File
@@ -497,6 +497,9 @@ describe CMockGenerator, "Verify CMockGenerator Module" do
:args => ["uint32 sandwiches", "const char* named"],
:var_arg => nil,
:name => "SupaFunction",
:unscoped_name => "SupaFunction",
:namespace => [],
:class => nil,
:attributes => "__inline"
}
output = []
@@ -532,6 +535,9 @@ describe CMockGenerator, "Verify CMockGenerator Module" do
:args => ["uint32 sandwiches"],
:var_arg => "corn ...",
:name => "SupaFunction",
:unscoped_name => "SupaFunction",
:namespace => [],
:class => nil,
:attributes => nil
}
output = []
@@ -558,4 +564,123 @@ describe CMockGenerator, "Verify CMockGenerator Module" do
assert_equal(expected.join, output.join)
end
it "create mock implementation function in source file for C++ static member" do
function = { :modifier => "static",
:return => test_return[:int],
:args_string => "uint32 sandwiches, const char* named",
:args => ["uint32 sandwiches", "const char* named"],
:var_arg => nil,
:unscoped_name => "SupaFunction",
:namespace => ["ns1", "ns2"],
:class => "SupaClass",
:name => "ns1_ns2_SupaClass_SupaFunction",
:attributes => nil
}
output = []
expected = [ "namespace ns1 {\n",
"namespace ns2 {\n",
"static int SupaClass::SupaFunction(uint32 sandwiches, const char* named)\n",
"{\n",
" UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;\n",
" CMOCK_ns1_ns2_SupaClass_SupaFunction_CALL_INSTANCE* cmock_call_instance;\n",
" UNITY_SET_DETAIL(CMockString_ns1_ns2_SupaClass_SupaFunction);\n",
" cmock_call_instance = (CMOCK_ns1_ns2_SupaClass_SupaFunction_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.ns1_ns2_SupaClass_SupaFunction_CallInstance);\n",
" Mock.ns1_ns2_SupaClass_SupaFunction_CallInstance = CMock_Guts_MemNext(Mock.ns1_ns2_SupaClass_SupaFunction_CallInstance);\n",
" uno",
" UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringCalledMore);\n",
" cmock_line = cmock_call_instance->LineNumber;\n",
" dos",
" tres",
" UNITY_CLR_DETAILS();\n",
" return cmock_call_instance->ReturnVal;\n",
"}\n",
"}\n",
"}\n\n",
]
@plugins.expect :run, [" uno"], [:mock_implementation_precheck, function]
@plugins.expect :run, [" dos"," tres"], [:mock_implementation, function]
@cmock_generator.create_mock_implementation(output, function)
assert_equal(expected.join, output.join)
end
it "create mock implementation functions in source file with different options for C++ static member" do
function = { :modifier => "static",
:c_calling_convention => "__stdcall",
:return => test_return[:int],
:args_string => "uint32 sandwiches",
:args => ["uint32 sandwiches"],
:var_arg => "corn ...",
:name => "SupaClass_SupaFunction",
:unscoped_name => "SupaFunction",
:namespace => [],
:class => "SupaClass",
:attributes => nil
}
output = []
expected = [ "static int __stdcall SupaClass::SupaFunction(uint32 sandwiches, corn ...)\n",
"{\n",
" UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;\n",
" CMOCK_SupaClass_SupaFunction_CALL_INSTANCE* cmock_call_instance;\n",
" UNITY_SET_DETAIL(CMockString_SupaClass_SupaFunction);\n",
" cmock_call_instance = (CMOCK_SupaClass_SupaFunction_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.SupaClass_SupaFunction_CallInstance);\n",
" Mock.SupaClass_SupaFunction_CallInstance = CMock_Guts_MemNext(Mock.SupaClass_SupaFunction_CallInstance);\n",
" uno",
" UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringCalledMore);\n",
" cmock_line = cmock_call_instance->LineNumber;\n",
" dos",
" tres",
" UNITY_CLR_DETAILS();\n",
" return cmock_call_instance->ReturnVal;\n",
"}\n\n"
]
@plugins.expect :run, [" uno"], [:mock_implementation_precheck, function]
@plugins.expect :run, [" dos"," tres"], [:mock_implementation, function]
@cmock_generator.create_mock_implementation(output, function)
assert_equal(expected.join, output.join)
end
it "creates using statements for C++ static member in namespace" do
function = { :modifier => "static",
:return => test_return[:int],
:args_string => "uint32 sandwiches",
:args => ["uint32 sandwiches"],
:var_arg => nil,
:unscoped_name => "SupaFunction",
:namespace => ["ns1"],
:class => "SupaClass",
:name => "ns1_SupaClass_SupaFunction",
:attributes => nil
}
output = []
expected = "using namespace ns1;\n"
@cmock_generator.create_using_statement(output, function)
assert_equal(expected, output.join)
end
it "creates using statements for C++ static member in nested namespace" do
function = { :modifier => "static",
:return => test_return[:int],
:args_string => "uint32 sandwiches",
:args => ["uint32 sandwiches"],
:var_arg => nil,
:unscoped_name => "SupaFunction",
:namespace => ["ns1", "ns2"],
:class => "SupaClass",
:name => "ns1_ns2_SupaClass_SupaFunction",
:attributes => nil
}
output = []
expected = "using namespace ns1::ns2;\n"
@cmock_generator.create_using_statement(output, function)
assert_equal(expected, output.join)
end
end
+519
View File
@@ -577,6 +577,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
source = "MY_FUNKY_VOID FunkyVoidReturned(int a)"
expected = { :var_arg=>nil,
:name=>"FunkyVoidReturned",
:unscoped_name=>"FunkyVoidReturned",
:namespace=>[],
:class=>nil,
:return=>{ :type => "void",
:name => 'cmock_to_return',
:ptr? => false,
@@ -597,6 +600,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
source = "int FunkyVoidAsArg(MY_FUNKY_VOID)"
expected = { :var_arg=>nil,
:name=>"FunkyVoidAsArg",
:unscoped_name=>"FunkyVoidAsArg",
:namespace=>[],
:class=>nil,
:return=>{ :type => "int",
:name => 'cmock_to_return',
:ptr? => false,
@@ -617,6 +623,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
source = "char FunkyVoidPointer(MY_FUNKY_VOID* bluh)"
expected = { :var_arg=>nil,
:name=>"FunkyVoidPointer",
:unscoped_name=>"FunkyVoidPointer",
:namespace=>[],
:class=>nil,
:return=>{ :type => "char",
:name => 'cmock_to_return',
:ptr? => false,
@@ -716,6 +725,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
source = "int Foo(int a, unsigned int b)"
expected = { :var_arg=>nil,
:name=>"Foo",
:unscoped_name=>"Foo",
:namespace=>[],
:class=>nil,
:return=>{ :type => "int",
:name => 'cmock_to_return',
:ptr? => false,
@@ -747,6 +759,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"FunkyChicken",
:unscoped_name=>"FunkyChicken",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"uint", :name=>"la", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -771,6 +786,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"tat",
:unscoped_name=>"tat",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ ],
@@ -792,6 +810,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"TheMatrix",
:unscoped_name=>"TheMatrix",
:namespace=>[],
:class=>nil,
:modifier=>"const",
:contains_ptr? => true,
:args=>[ {:type=>"int", :name=>"Trinity", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -815,6 +836,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"TheMatrix",
:unscoped_name=>"TheMatrix",
:namespace=>[],
:class=>nil,
:modifier=>"const",
:c_calling_convention=>"__stdcall",
:contains_ptr? => true,
@@ -826,6 +850,31 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
assert_equal(expected, @parser.parse_declaration(source))
end
it "extract and return function declarations inside namespace and class" do
source = "int Foo(int a, unsigned int b)"
expected = { :var_arg=>nil,
:name=>"ns1_ns2_Bar_Foo",
:unscoped_name=>"Foo",
:class=>"Bar",
:namespace=>["ns1", "ns2"],
:return=>{ :type => "int",
:name => 'cmock_to_return',
:ptr? => false,
:const? => false,
:const_ptr? => false,
:str => "int cmock_to_return",
:void? => false
},
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"int", :name=>"a", :ptr? => false, :const? => false, :const_ptr? => false},
{:type=>"unsigned int", :name=>"b", :ptr? => false, :const? => false, :const_ptr? => false}
],
:args_string=>"int a, unsigned int b",
:args_call=>"a, b" }
assert_equal(expected, @parser.parse_declaration(source, ["ns1", "ns2"], "Bar"))
end
it "fully parse multiple prototypes" do
source = "const int TheMatrix(int Trinity, unsigned int * Neo);\n" +
@@ -841,6 +890,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"TheMatrix",
:unscoped_name=>"TheMatrix",
:namespace=>[],
:class=>nil,
:modifier=>"const",
:contains_ptr? => true,
:args=>[ {:type=>"int", :name=>"Trinity", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -858,6 +910,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"Morpheus",
:unscoped_name=>"Morpheus",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args=>[ {:type=>"int", :name=>"cmock_arg1", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -876,6 +931,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
expected = [{ :var_arg=>nil,
:name=>"TheMatrix",
:unscoped_name=>"TheMatrix",
:namespace=>[],
:class=>nil,
:return=> { :type => "int",
:name => 'cmock_to_return',
:ptr? => false,
@@ -904,6 +962,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
expected = [{ :var_arg => nil,
:name => "PorkRoast",
:unscoped_name => "PorkRoast",
:namespace=>[],
:class=>nil,
:return => { :type => "const int*",
:name => 'cmock_to_return',
:ptr? => true,
@@ -933,6 +994,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
expected = [{ :var_arg => nil,
:name => "PorkRoast",
:unscoped_name => "PorkRoast",
:namespace=>[],
:class=>nil,
:return => { :type => "int const*",
:name => 'cmock_to_return',
:ptr? => true,
@@ -959,6 +1023,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
expected = [{ :var_arg=>nil,
:name=>"PorkRoast",
:unscoped_name=>"PorkRoast",
:namespace=>[],
:class=>nil,
:return=> { :type => "int*",
:name => 'cmock_to_return',
:ptr? => true,
@@ -981,6 +1048,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
source = "void foo(int const*, int*const, const int*, const int*const, int const*const, int*, int, const int);\n"
expected = [{ :name => "foo",
:unscoped_name => "foo",
:namespace=>[],
:class=>nil,
:modifier => "",
:return => { :type => "void",
:name => "cmock_to_return",
@@ -1013,6 +1083,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
" int const*const param5, int*param6, int param7, const int param8);\n"
expected = [{ :name => "bar",
:unscoped_name => "bar",
:namespace=>[],
:class=>nil,
:modifier => "",
:return => { :type => "void",
:name => "cmock_to_return",
@@ -1044,6 +1117,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
source = "Book AddToBook(Book book, const IntArray values);\n"
expected = [{ :name => "AddToBook",
:unscoped_name => "AddToBook",
:namespace=>[],
:class=>nil,
:modifier=>"",
:return => { :type => "Book",
:name => "cmock_to_return",
@@ -1074,6 +1150,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
expected = [{ :var_arg=>nil,
:name=>"DrHorrible",
:unscoped_name=>"DrHorrible",
:namespace=>[],
:class=>nil,
:return => { :type => "void",
:name => 'cmock_to_return',
:ptr? => false,
@@ -1098,6 +1177,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"CaptainHammer",
:unscoped_name=>"CaptainHammer",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ ],
@@ -1123,6 +1205,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"DrHorrible",
:unscoped_name=>"DrHorrible",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"struct SingAlong", :name=>"Blog", :ptr? => false, :const? => false, :const_ptr? => false} ],
@@ -1139,6 +1224,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"Penny",
:unscoped_name=>"Penny",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args=>[ {:type=>"struct const _KeepYourHeadUp_*", :name=>"BillyBuddy", :ptr? => true, :const? => true, :const_ptr? => true} ],
@@ -1155,6 +1243,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"CaptainHammer",
:unscoped_name=>"CaptainHammer",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ ],
@@ -1176,6 +1267,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"OrangePeel",
:unscoped_name=>"OrangePeel",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args=>[ {:type=>"union STARS_AND_STRIPES*", :name=>"a", :ptr? => true, :const? => false, :const_ptr? => false},
@@ -1199,6 +1293,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"ApplePeel",
:unscoped_name=>"ApplePeel",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args=>[ {:type=> "unsigned int", :name=>"const_param", :ptr? => false, :const? => true, :const_ptr? => false},
@@ -1225,6 +1322,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"LemonPeel",
:unscoped_name=>"LemonPeel",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args=>[ {:type=>"integer", :name=>"param", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -1251,6 +1351,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"CoinOperated",
:unscoped_name=>"CoinOperated",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"signed char", :name=>"abc", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -1276,6 +1379,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"CardOperated",
:unscoped_name=>"CardOperated",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args=>[ {:type=>"CUSTOM_TYPE", :name=>"abc", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -1311,6 +1417,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"KeyOperated",
:unscoped_name=>"KeyOperated",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args => expected_args,
@@ -1333,6 +1442,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"Cheese",
:unscoped_name=>"Cheese",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"unsigned CUSTOM_TYPE", :name=>"abc", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -1357,6 +1469,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"FunkyTurkey",
:unscoped_name=>"FunkyTurkey",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"cmock_module_func_ptr1", :name=>"func_ptr", :ptr? => false, :const? => false, :const_ptr? => false}
@@ -1381,6 +1496,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"FunkyTurkey",
:unscoped_name=>"FunkyTurkey",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"cmock_module_func_ptr1", :name=>"func_ptr", :ptr? => false, :const? => false, :const_ptr? => false}
@@ -1405,6 +1523,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"FunkyTurkey",
:unscoped_name=>"FunkyTurkey",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"cmock_module_func_ptr1", :name=>"func_ptr", :ptr? => false, :const? => false, :const_ptr? => false}
@@ -1429,6 +1550,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"FunkyTurkey",
:unscoped_name=>"FunkyTurkey",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"cmock_module_func_ptr1", :name=>"func_ptr", :ptr? => false, :const? => false, :const_ptr? => false}
@@ -1453,6 +1577,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"FunkyChicken",
:unscoped_name=>"FunkyChicken",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"cmock_module_func_ptr1", :name=>"func_ptr", :ptr? => false, :const? => true, :const_ptr? => false}
@@ -1477,6 +1604,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
# :void? => true
# },
# :name=>"FunkyParrot",
# :unscoped_name=>"FunkyParrot",
# :namespace=>[],
# :class=>nil,
# :modifier=>"",
# :contains_ptr? => false,
# :args=>[ {:type=>"cmock_module_func_ptr1", :name=>"func_ptr", :ptr? => false, :const? => false, :const_ptr? => false}
@@ -1501,6 +1631,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"FunkyBudgie",
:unscoped_name=>"FunkyBudgie",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"cmock_module_func_ptr1", :name=>"func_ptr1", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -1526,6 +1659,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"FunkyRobin",
:unscoped_name=>"FunkyRobin",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"uint16_t", :name=>"num1", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -1552,6 +1688,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => true
},
:name=>"FunkyFowl",
:unscoped_name=>"FunkyFowl",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"cmock_module_func_ptr1", :name=>"cmock_arg1", :ptr? => false, :const? => true, :const_ptr? => false}
@@ -1576,6 +1715,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"FunkyPidgeon",
:unscoped_name=>"FunkyPidgeon",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"char", :name=>"op_code", :ptr? => false, :const? => true, :const_ptr? => false}
@@ -1600,6 +1742,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"FunkyTweetie",
:unscoped_name=>"FunkyTweetie",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[],
@@ -1623,6 +1768,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"FunkySeaGull",
:unscoped_name=>"FunkySeaGull",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[],
@@ -1646,6 +1794,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"FunkyMacaw",
:unscoped_name=>"FunkyMacaw",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args=>[ {:type=>"double*", :name=>"foo", :ptr? => true, :const? => false, :const_ptr? => false},
@@ -1671,6 +1822,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"sqlite3_bind_text",
:unscoped_name=>"sqlite3_bind_text",
:namespace=>[],
:class=>nil,
:modifier=>"SQLITE_API",
:contains_ptr? => true,
:args=>[ {:type=>"sqlite3_stmt*", :name=>"cmock_arg2", :ptr? => true, :const? => false, :const_ptr? => false},
@@ -1699,6 +1853,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"XFiles",
:unscoped_name=>"XFiles",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"int", :name=>"Scully", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -1722,6 +1879,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"MoreSillySongs",
:unscoped_name=>"MoreSillySongs",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => true,
:args=>[ {:type=>"void*", :name=>"stuff", :ptr? => true, :const? => false, :const_ptr? => false}
@@ -1744,6 +1904,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"LaverneAndShirley",
:unscoped_name=>"LaverneAndShirley",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"int", :name=>"Lenny", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -1767,6 +1930,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"TheCosbyShow",
:unscoped_name=>"TheCosbyShow",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"int", :name=>"Cliff", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -2072,6 +2238,9 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
:void? => false
},
:name=>"LaverneAndShirley",
:unscoped_name=>"LaverneAndShirley",
:namespace=>[],
:class=>nil,
:modifier=>"",
:contains_ptr? => false,
:args=>[ {:type=>"int", :name=>"Lenny", :ptr? => false, :const? => false, :const_ptr? => false},
@@ -2083,6 +2252,356 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "imports C++ differently when asked" do
source =
[
"namespace ns1 {\n",
" namespace ns2 {\n",
"\n",
" class cls1 {\n",
" public:\n",
" int f_header_impl(int a, int b){\n",
" return a + b;\n",
" }\n",
"\n",
" static void f_void();\n",
" static int f_ret_simple();\n",
"\n",
" protected:\n",
" static void protected_f_void();\n",
"\n",
" public:\n",
" private:\n",
" static void private_f_void();\n",
" }; // cls1\n",
" } // ns2\n",
"} // ns1\n"
].join
expected =
[
"namespace ns1 { namespace ns2 { class cls1 { public: int f_header_impl",
"static void f_void()",
"static int f_ret_simple()",
"protected: static void protected_f_void()",
"public: private: static void private_f_void()",
"}",
"} }"
]
assert_equal(expected, @parser.import_source(source, cpp=true))
refute_equal(expected, @parser.import_source(source))
end
# only so parse_functions does not raise an error
def dummy_source
"void dummy(void);"
end
# Expected result of above
def dummy_func
{ :name => "dummy",
:unscoped_name => "dummy",
:class => nil,
:namespace => [],
:var_arg => nil,
:args_string => "void",
:args => [],
:args_call => "",
:contains_ptr? => false,
:modifier => "",
:return => {
:type => "void",
:name => "cmock_to_return",
:str => "void cmock_to_return",
:void? => true,
:ptr? => false,
:const? => false,
:const_ptr? => false}}
end
# Commonly used example function
def voidvoid_func(namespace=[], name="Classic_functional")
{ :name => name,
:unscoped_name => "functional",
:class => "Classic",
:namespace => namespace,
:var_arg => nil,
:args_string => "void",
:args => [],
:args_call => "",
:contains_ptr? => false,
:modifier => "",
:return => {
:type=>"void",
:name=>"cmock_to_return",
:str=>"void cmock_to_return",
:void? => true,
:ptr? => false,
:const? => false,
:const_ptr? => false}}
end
it "parses public C++ functions" do
source = dummy_source + <<~SOURCE
class Classic {
public:
static void functional(void);
};
SOURCE
expected = [dummy_func, voidvoid_func]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "handles a namespace" do
source = dummy_source + <<~SOURCE
namespace ns1 {
class Classic {
public:
static void functional(void);
};
} // ns1
SOURCE
expected = [dummy_func,
voidvoid_func(namespace=["ns1"], name="ns1_Classic_functional")]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "handles nested namespaces" do
source = dummy_source + <<~SOURCE
namespace ns1 {
namespace ns2 {
class Classic {
public:
static void functional(void);
};
} // ns1
} // ns1
SOURCE
expected = [dummy_func,
voidvoid_func(namespace=["ns1", "ns2"], name="ns1_ns2_Classic_functional")]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "ignores non-static C++ functions" do
source = dummy_source + <<~SOURCE
class Classic {
public:
void functional(void);
};
SOURCE
expected = [dummy_func]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "ignores private functions" do
source = dummy_source + <<~SOURCE
class Classic {
private:
static void functional(void);
};
SOURCE
expected = [dummy_func]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "parses public C++ functions after private functions" do
source = dummy_source + <<~SOURCE
class Classic {
private:
static void ignoreme(void);
public:
static void functional(void);
};
SOURCE
expected = [dummy_func, voidvoid_func]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "ignores protected functions" do
source = dummy_source + <<~SOURCE
class Classic {
protected:
static void functional(void);
};
SOURCE
expected = [dummy_func]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "parses public C++ functions after protected functions" do
source = dummy_source + <<~SOURCE
class Classic {
protected:
static void ignoreme(void);
public:
static void functional(void);
};
SOURCE
expected = [dummy_func, voidvoid_func]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "parses multiple classes in same file with uniquely named functions" do
source = dummy_source + <<~SOURCE
namespace ns1 {
class Classic {
public:
static void functional(void);
};
class Classical {
public:
static int functionality(int a);
};
} // ns1
class Classy {
public:
static int* func(int* a);
};
SOURCE
expected = [dummy_func,
voidvoid_func(["ns1"], name="ns1_Classic_functional"),
{ :name => "ns1_Classical_functionality",
:unscoped_name => "functionality",
:class => "Classical",
:namespace => ["ns1"],
:var_arg => nil,
:args_string => "int a",
:args => [
{ :ptr? => false,
:const? => false,
:const_ptr? => false,
:name => "a",
:type => "int"}],
:args_call => "a",
:contains_ptr? => false,
:modifier => "",
:return => {
:type=>"int",
:name=>"cmock_to_return",
:str=>"int cmock_to_return",
:void? => false,
:ptr? => false,
:const? => false,
:const_ptr? => false}},
{ :name => "Classy_func",
:unscoped_name => "func",
:class => "Classy",
:namespace => [],
:var_arg => nil,
:args_string => "int* a",
:args => [
{ :ptr? => true,
:const? => false,
:const_ptr? => false,
:name => "a",
:type => "int*"}],
:args_call => "a",
:contains_ptr? => true,
:modifier => "",
:return => {
:type=>"int*",
:name=>"cmock_to_return",
:str=>"int* cmock_to_return",
:void? => false,
:ptr? => true,
:const? => false,
:const_ptr? => false}}]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "handles multiple classes in same file with identically named functions" do
source = dummy_source + <<~SOURCE
namespace ns1 {
class Classic {
public:
static void functional(void);
};
class Classical {
public:
static int functional(int a);
};
} // ns1
class Classy {
public:
static int* functional(int* a);
};
SOURCE
expected = [dummy_func,
voidvoid_func(["ns1"], name="ns1_Classic_functional"),
{ :name => "ns1_Classical_functional",
:unscoped_name => "functional",
:class => "Classical",
:namespace => ["ns1"],
:var_arg => nil,
:args_string => "int a",
:args => [
{ :ptr? => false,
:const? => false,
:const_ptr? => false,
:name => "a",
:type => "int"}],
:args_call => "a",
:contains_ptr? => false,
:modifier => "",
:return => {
:type=>"int",
:name=>"cmock_to_return",
:str=>"int cmock_to_return",
:void? => false,
:ptr? => false,
:const? => false,
:const_ptr? => false}},
{ :name => "Classy_functional",
:unscoped_name => "functional",
:class => "Classy",
:namespace => [],
:var_arg => nil,
:args_string => "int* a",
:args => [
{ :ptr? => true,
:const? => false,
:const_ptr? => false,
:name => "a",
:type => "int*"}],
:args_call => "a",
:contains_ptr? => true,
:modifier => "",
:return => {
:type=>"int*",
:name=>"cmock_to_return",
:str=>"int* cmock_to_return",
:void? => false,
:ptr? => true,
:const? => false,
:const_ptr? => false}}]
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "Transform inline functions can handle inline function declarations" do
source =
"static inline int dummy_func_decl(int a, char b, float c);\n" + # First declaration