Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for C++ reference type for return values (but not arguments) #317

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/cmock_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ def create_source_header_section(file, filename, functions)
file << "#include <string.h>\n"
file << "#include <stdlib.h>\n"
file << "#include <setjmp.h>\n"
file << "#ifdef __cplusplus\n"
file << "#include <functional>\n"
file << "#endif\n"
file << "#include \"cmock.h\"\n"
@includes_c_pre_header.each { |inc| file << "#include #{inc}\n" }
file << "#include \"#{header_file}\"\n"
Expand Down Expand Up @@ -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 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
file << functions.collect { |function| @plugins.run(:mock_destroy, function) }.join

unless @fail_on_unexpected_calls
Expand Down
7 changes: 7 additions & 0 deletions lib/cmock_generator_plugin_callback.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 7 additions & 1 deletion lib/cmock_generator_plugin_expect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions lib/cmock_generator_plugin_ignore.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/cmock_generator_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion lib/cmock_header_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'},
}
Expand All @@ -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},
Expand Down
7 changes: 5 additions & 2 deletions test/unit/cmock_generator_main_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,9 @@ def helper_create_header_top_with_opt_incldues_form_config_and_plugin(ext)
"#include <string.h>\n",
"#include <stdlib.h>\n",
"#include <setjmp.h>\n",
"#ifdef __cplusplus\n",
"#include <functional>\n",
"#endif\n",
"#include \"cmock.h\"\n",
"#include \"MockPoutPoutFish.h\"\n",
"\n",
Expand Down Expand Up @@ -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"
]

Expand All @@ -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",
Expand Down
10 changes: 10 additions & 0 deletions test/unit/cmock_generator_plugin_callback_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions test/unit/cmock_generator_plugin_expect_a_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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" +
Expand Down
10 changes: 10 additions & 0 deletions test/unit/cmock_generator_plugin_expect_b_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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" +
Expand Down
19 changes: 19 additions & 0 deletions test/unit/cmock_generator_plugin_ignore_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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" +
Expand Down
8 changes: 8 additions & 0 deletions test/unit/cmock_generator_utils_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
68 changes: 68 additions & 0 deletions test/unit/cmock_header_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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()",
"}",
Expand Down Expand Up @@ -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 {
Expand Down