diff --git a/docs/CMock_Summary.md b/docs/CMock_Summary.md index 7d2b5e4..0b42e2f 100644 --- a/docs/CMock_Summary.md +++ b/docs/CMock_Summary.md @@ -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 ======== diff --git a/lib/cmock_generator.rb b/lib/cmock_generator.rb index da9d587..f08cadd 100644 --- a/lib/cmock_generator.rb +++ b/lib/cmock_generator.rb @@ -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) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 632513d..8638a04 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -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] diff --git a/test/unit/cmock_generator_main_test.rb b/test/unit/cmock_generator_main_test.rb index 963aee2..8462412 100644 --- a/test/unit/cmock_generator_main_test.rb +++ b/test/unit/cmock_generator_main_test.rb @@ -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 diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index ccc00f4..5d5d2fc 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -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