From 4d67662e5100ad58da2748ed7ec092cdbc40a37c Mon Sep 17 00:00:00 2001 From: Tuc-An Date: Mon, 20 Jul 2020 12:03:39 -0400 Subject: [PATCH 1/2] Add support for C++ reference type for return values (but not arguments) --- lib/cmock_generator.rb | 8 ++- lib/cmock_generator_plugin_callback.rb | 7 ++ lib/cmock_generator_plugin_expect.rb | 8 ++- lib/cmock_generator_plugin_ignore.rb | 2 + lib/cmock_generator_utils.rb | 2 +- lib/cmock_header_parser.rb | 2 +- test/test_helper.rb | 2 + test/unit/cmock_generator_main_test.rb | 7 +- .../cmock_generator_plugin_callback_test.rb | 10 +++ .../cmock_generator_plugin_expect_a_test.rb | 9 +++ .../cmock_generator_plugin_expect_b_test.rb | 10 +++ .../cmock_generator_plugin_ignore_test.rb | 19 ++++++ test/unit/cmock_generator_utils_test.rb | 8 +++ test/unit/cmock_header_parser_test.rb | 68 +++++++++++++++++++ 14 files changed, 156 insertions(+), 6 deletions(-) diff --git a/lib/cmock_generator.rb b/lib/cmock_generator.rb index f08cadd7..158aaea1 100644 --- a/lib/cmock_generator.rb +++ b/lib/cmock_generator.rb @@ -170,6 +170,9 @@ def create_source_header_section(file, filename, functions) file << "#include \n" file << "#include \n" file << "#include \n" + file << "#ifdef __cplusplus\n" + file << "#include \n" + file << "#endif\n" file << "#include \"cmock.h\"\n" @includes_c_pre_header.each { |inc| file << "#include #{inc}\n" } file << "#include \"#{header_file}\"\n" @@ -236,7 +239,10 @@ def create_mock_init_function(file) def create_mock_destroy_function(file, functions) file << "void #{@clean_mock_name}_Destroy(void)\n{\n" file << " CMock_Guts_MemFreeAll();\n" - file << " memset(&Mock, 0, sizeof(Mock));\n" + # NOTE: zeroing an object may result in crashes so we muct be selective when erasing this structure + functions.each do |function| + file << " memset(&Mock.#{function[:name]}_CallInstance, 0, sizeof(Mock.#{function[:name]}_CallInstance));\n" + end file << functions.collect { |function| @plugins.run(:mock_destroy, function) }.join unless @fail_on_unexpected_calls diff --git a/lib/cmock_generator_plugin_callback.rb b/lib/cmock_generator_plugin_callback.rb index 6ba8e9bd..4bc6c718 100644 --- a/lib/cmock_generator_plugin_callback.rb +++ b/lib/cmock_generator_plugin_callback.rb @@ -85,4 +85,11 @@ def mock_verify(function) " call_instance = CMOCK_GUTS_NONE;\n" \ " (void)call_instance;\n }\n" end + + def mock_destroy(function) + func_name = function[:name] + " Mock.#{func_name}_CallbackBool = (char)0;\n" \ + " Mock.#{func_name}_CallbackFunctionPointer = NULL;\n" \ + " Mock.#{func_name}_CallbackCalls = 0;\n" + end end diff --git a/lib/cmock_generator_plugin_expect.rb b/lib/cmock_generator_plugin_expect.rb index 3a79c1a4..befdc500 100644 --- a/lib/cmock_generator_plugin_expect.rb +++ b/lib/cmock_generator_plugin_expect.rb @@ -25,7 +25,13 @@ def initialize(config, utils) def instance_typedefs(function) lines = '' - lines << " #{function[:return][:type]} ReturnVal;\n" unless function[:return][:void?] + if function[:return][:type].end_with?('&') + # Handle C++ reference return type differently + lines << " #{function[:return][:type].chomp('&')} ReturnRefVal;\n" + lines << " std::reference_wrapper<#{function[:return][:type].chomp('&')}> ReturnVal = ReturnRefVal;\n" + else + lines << " #{function[:return][:type]} ReturnVal;\n" unless function[:return][:void?] + end lines << " int CallOrder;\n" if @ordered function[:args].each do |arg| lines << " #{arg[:type]} Expected_#{arg[:name]};\n" diff --git a/lib/cmock_generator_plugin_ignore.rb b/lib/cmock_generator_plugin_ignore.rb index b292f3d4..69fbb60c 100644 --- a/lib/cmock_generator_plugin_ignore.rb +++ b/lib/cmock_generator_plugin_ignore.rb @@ -17,6 +17,8 @@ def initialize(config, utils) def instance_structure(function) if function[:return][:void?] " char #{function[:name]}_IgnoreBool;\n" + elsif function[:return][:type].end_with?('&') # C++ reference + " char #{function[:name]}_IgnoreBool;\n #{function[:return][:type].chomp('&')} #{function[:name]}_FinalRefReturn;\n std::reference_wrapper<#{function[:return][:type].chomp('&')}> #{function[:name]}_FinalReturn = #{function[:name]}_FinalRefReturn;\n" else " char #{function[:name]}_IgnoreBool;\n #{function[:return][:type]} #{function[:name]}_FinalReturn;\n" end diff --git a/lib/cmock_generator_utils.rb b/lib/cmock_generator_utils.rb index 11225d6e..2ddbaedd 100644 --- a/lib/cmock_generator_utils.rb +++ b/lib/cmock_generator_utils.rb @@ -69,7 +69,7 @@ def code_add_an_arg_expectation(arg, depth = 1) end def code_assign_argument_quickly(dest, arg) - if arg[:ptr?] || @treat_as.include?(arg[:type]) + if arg[:ptr?] || @treat_as.include?(arg[:type]) || arg[:type].end_with?('&') " #{dest} = #{arg[:name]};\n" else assert_expr = "sizeof(#{arg[:name]}) == sizeof(#{arg[:type]}) ? 1 : -1" diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 8638a04f..631e51db 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -15,7 +15,7 @@ def initialize(cfg) @c_calling_conventions = cfg.c_calling_conventions.uniq @treat_as_array = cfg.treat_as_array @treat_as_void = (['void'] + cfg.treat_as_void).uniq - @function_declaration_parse_base_match = '([\w\s\*\(\),\[\]]+??)\(([\w\s\*\(\),\.\[\]+\-\/]*)\)' + @function_declaration_parse_base_match = '([\w\s\*&\(\),\[\]]+??)\(([\w\s\*\(\),\.\[\]+\-\/]*)\)' @declaration_parse_matcher = /#{@function_declaration_parse_base_match}$/m @standards = (%w[int short char long unsigned signed] + cfg.treat_as.keys).uniq @array_size_name = cfg.array_size_name diff --git a/test/test_helper.rb b/test/test_helper.rb index 486196c0..ffbf4ad8 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -26,6 +26,7 @@ def test_return { :int => {:type => "int", :name => 'cmock_to_return', :ptr? => false, :const? => false, :void? => false, :str => 'int cmock_to_return'}, :int_ptr => {:type => "int*", :name => 'cmock_to_return', :ptr? => true, :const? => false, :void? => false, :str => 'int* cmock_to_return'}, + :int_ref => {:type => "int&", :name => 'cmock_to_return', :ptr? => true, :const? => false, :void? => false, :str => 'int& cmock_to_return'}, :void => {:type => "void", :name => 'cmock_to_return', :ptr? => false, :const? => false, :void? => true, :str => 'void cmock_to_return'}, :string => {:type => "const char*", :name => 'cmock_to_return', :ptr? => false, :const? => true, :void? => false, :str => 'const char* cmock_to_return'}, } @@ -35,6 +36,7 @@ def test_arg { :int => {:type => "int", :name => 'MyInt', :ptr? => false, :const? => false, :const_ptr? => false}, :int_ptr => {:type => "int*", :name => 'MyIntPtr', :ptr? => true, :const? => false, :const_ptr? => false}, + :int_ref => {:type => "int&", :name => 'MyIntRef', :ptr? => true, :const? => false, :const_ptr? => false}, :const_ptr => {:type => "int*", :name => 'MyConstPtr', :ptr? => true, :const? => false, :const_ptr? => true}, :double_ptr => {:type => "int const**", :name => 'MyDoublePtr', :ptr? => true, :const? => true, :const_ptr? => false}, :mytype => {:type => "MY_TYPE", :name => 'MyMyType', :ptr? => false, :const? => true, :const_ptr? => false}, diff --git a/test/unit/cmock_generator_main_test.rb b/test/unit/cmock_generator_main_test.rb index 84624125..dce41a53 100644 --- a/test/unit/cmock_generator_main_test.rb +++ b/test/unit/cmock_generator_main_test.rb @@ -325,6 +325,9 @@ def helper_create_header_top_with_opt_incldues_form_config_and_plugin(ext) "#include \n", "#include \n", "#include \n", + "#ifdef __cplusplus\n", + "#include \n", + "#endif\n", "#include \"cmock.h\"\n", "#include \"MockPoutPoutFish.h\"\n", "\n", @@ -460,7 +463,6 @@ def helper_create_header_top_with_opt_incldues_form_config_and_plugin(ext) output = [] expected = [ "void MockPoutPoutFish_Destroy(void)\n{\n", " CMock_Guts_MemFreeAll();\n", - " memset(&Mock, 0, sizeof(Mock));\n", "}\n\n" ] @@ -476,7 +478,8 @@ def helper_create_header_top_with_opt_incldues_form_config_and_plugin(ext) output = [] expected = [ "void MockPoutPoutFish_Destroy(void)\n{\n", " CMock_Guts_MemFreeAll();\n", - " memset(&Mock, 0, sizeof(Mock));\n", + " memset(&Mock.First_CallInstance, 0, sizeof(Mock.First_CallInstance));\n", + " memset(&Mock.Second_CallInstance, 0, sizeof(Mock.Second_CallInstance));\n", " uno", " GlobalExpectCount = 0;\n", " GlobalVerifyOrder = 0;\n", diff --git a/test/unit/cmock_generator_plugin_callback_test.rb b/test/unit/cmock_generator_plugin_callback_test.rb index 0b5ce1eb..5f1cb9fd 100644 --- a/test/unit/cmock_generator_plugin_callback_test.rb +++ b/test/unit/cmock_generator_plugin_callback_test.rb @@ -278,4 +278,14 @@ returned = @cmock_generator_plugin_callback.mock_interfaces(function) assert_equal(expected, returned) end + + it "add mock destruction for function" do + function = {:name => "Apple", :args => [], :args_string => "void", :return => test_return[:int]} + expected = [" Mock.Apple_CallbackBool = (char)0;\n", + " Mock.Apple_CallbackFunctionPointer = NULL;\n", + " Mock.Apple_CallbackCalls = 0;\n" + ].join + returned = @cmock_generator_plugin_callback.mock_destroy(function) + assert_equal(expected, returned) + end end diff --git a/test/unit/cmock_generator_plugin_expect_a_test.rb b/test/unit/cmock_generator_plugin_expect_a_test.rb index dd86689a..f8662ad8 100644 --- a/test/unit/cmock_generator_plugin_expect_a_test.rb +++ b/test/unit/cmock_generator_plugin_expect_a_test.rb @@ -62,6 +62,15 @@ assert_equal(expected, returned) end + it "add to typedef structure mock needs of functions of with C++ reference return type" do + function = {:name => "Elm", :args => [], :return => test_return[:int_ref]} + expected = [" int ReturnRefVal;\n", + " std::reference_wrapper ReturnVal = ReturnRefVal;\n" + ].join + returned = @cmock_generator_plugin_expect.instance_typedefs(function) + assert_equal(expected, returned) + end + it "add mock function declaration for functions of style 'void func(void)'" do function = {:name => "Maple", :args => [], :return => test_return[:void]} expected = "#define Maple_Expect() Maple_CMockExpect(__LINE__)\n" + diff --git a/test/unit/cmock_generator_plugin_expect_b_test.rb b/test/unit/cmock_generator_plugin_expect_b_test.rb index 35d48715..9de693df 100644 --- a/test/unit/cmock_generator_plugin_expect_b_test.rb +++ b/test/unit/cmock_generator_plugin_expect_b_test.rb @@ -62,6 +62,16 @@ assert_equal(expected, returned) end + it "add to typedef structure mock needs of functions of with C++ reference return type" do + function = {:name => "Elm", :args => [], :return => test_return[:int_ref]} + expected = [" int ReturnRefVal;\n", + " std::reference_wrapper ReturnVal = ReturnRefVal;\n", + " int CallOrder;\n" + ].join + returned = @cmock_generator_plugin_expect.instance_typedefs(function) + assert_equal(expected, returned) + end + it "add mock function declaration for functions of style 'void func(void)'" do function = {:name => "Maple", :args => [], :return => test_return[:void]} expected = "#define Maple_Expect() Maple_CMockExpect(__LINE__)\n" + diff --git a/test/unit/cmock_generator_plugin_ignore_test.rb b/test/unit/cmock_generator_plugin_ignore_test.rb index c0c28b84..775ae494 100644 --- a/test/unit/cmock_generator_plugin_ignore_test.rb +++ b/test/unit/cmock_generator_plugin_ignore_test.rb @@ -33,6 +33,25 @@ assert_equal(expected, returned) end + it "add required variables to the instance structure for simple non-void return type" do + function = {:name => "Grass", :args => [], :return => test_return[:int]} + expected = [" char Grass_IgnoreBool;\n", + " int Grass_FinalReturn;\n" + ].join + returned = @cmock_generator_plugin_ignore.instance_structure(function) + assert_equal(expected, returned) + end + + it "add required variables to the instance structure for C++ reference return type" do + function = {:name => "Grass", :args => [], :return => test_return[:int_ref]} + expected = [" char Grass_IgnoreBool;\n", + " int Grass_FinalRefReturn;\n", + " std::reference_wrapper Grass_FinalReturn = Grass_FinalRefReturn;\n" + ].join + returned = @cmock_generator_plugin_ignore.instance_structure(function) + assert_equal(expected, returned) + end + it "handle function declarations for functions without return values" do function = {:name => "Mold", :args_string => "void", :return => test_return[:void]} expected = "#define Mold_Ignore() Mold_CMockIgnore()\nvoid Mold_CMockIgnore(void);\n" + diff --git a/test/unit/cmock_generator_utils_test.rb b/test/unit/cmock_generator_utils_test.rb index ab9df2a2..fae28a8a 100644 --- a/test/unit/cmock_generator_utils_test.rb +++ b/test/unit/cmock_generator_utils_test.rb @@ -395,4 +395,12 @@ @unity_helper.expect :get_helper, ['UNITY_TEST_ASSERT_EQUAL_MY_TYPE_ARRAY', '&'], ['MY_TYPE'] assert_equal(expected, @cmock_generator_utils_complex.code_verify_an_arg_expectation(function, arg)) end + + it 'handle C++ reference like a pointer in regards to copying/assigning (in code_assign_argument_quickly)' do + dest = 'somewhereWarm' + arg = test_arg[:int_ref] + expected = " somewhereWarm = MyIntRef;\n" + output = @cmock_generator_utils_simple.code_assign_argument_quickly(dest, arg) + assert_equal(expected, output) + end end diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index 5d5d2fcc..c4c75ff0 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -2266,6 +2266,7 @@ "\n", " static void f_void();\n", " static int f_ret_simple();\n", + " static int& f_ret_ref();\n", "\n", " protected:\n", " static void protected_f_void();\n", @@ -2283,6 +2284,7 @@ "namespace ns1 { namespace ns2 { class cls1 { public: int f_header_impl", "static void f_void()", "static int f_ret_simple()", + "static int& f_ret_ref()", "protected: static void protected_f_void()", "public: private: static void private_f_void()", "}", @@ -2458,6 +2460,72 @@ class Classic { assert_equal(expected, @parser.parse("module", source)[:functions]) end + it "parses functions with a reference return type" do + source = <<~SOURCE + int& dummy(void); + + class Classic { + public: + static int& functional(void); + }; + SOURCE + + # Verify both classic C and C++ examples + expected = [ + { :name => "dummy", + :unscoped_name => "dummy", + :class => nil, + :namespace => [], + :var_arg => nil, + :args_string => "void", + :args => [], + :args_call => "", + :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 => "Classic_functional", + :unscoped_name => "functional", + :class => "Classic", + :namespace => [], + :var_arg => nil, + :args_string => "void", + :args => [], + :args_call => "", + :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}}] + + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + + # Known limitation - would be great to overcome in future + it "cannot handle reference type args" do + source = dummy_source + <<~SOURCE + class Classic { + public: + static void functional(int& a); + }; + SOURCE + + expected = [dummy_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 { From 2bf9b657fcbdc3b416ce44f508915998f12c5171 Mon Sep 17 00:00:00 2001 From: Jsun Date: Wed, 9 Dec 2020 15:20:40 -0500 Subject: [PATCH 2/2] Update cmock_generator.rb --- lib/cmock_generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cmock_generator.rb b/lib/cmock_generator.rb index 158aaea1..a889b42f 100644 --- a/lib/cmock_generator.rb +++ b/lib/cmock_generator.rb @@ -239,7 +239,7 @@ def create_mock_init_function(file) def create_mock_destroy_function(file, functions) file << "void #{@clean_mock_name}_Destroy(void)\n{\n" file << " CMock_Guts_MemFreeAll();\n" - # NOTE: zeroing an object may result in crashes so we muct be selective when erasing this structure + # NOTE: zeroing an object may result in crashes so we must be selective when erasing this structure functions.each do |function| file << " memset(&Mock.#{function[:name]}_CallInstance, 0, sizeof(Mock.#{function[:name]}_CallInstance));\n" end