diff --git a/test/system/test_interactions/const_ptr_return_and_arg.yml b/test/system/test_interactions/const_ptr_return_and_arg.yml new file mode 100644 index 0000000..bc43b70 --- /dev/null +++ b/test/system/test_interactions/const_ptr_return_and_arg.yml @@ -0,0 +1,80 @@ +# ========================================================================= +# CMock - Automatic Mock Generation for C +# ThrowTheSwitch.org +# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:cmock: + :plugins: + - # none + +:systest: + :types: | + + :mockable: | + int *const foo(float const *const self); + + :source: + :header: | + int *const exercise(float const *const self); + + :code: | + int *const exercise(float const *const self) + { + return foo(self); + } + + :tests: + :common: | + static float f1 = 1.0f; + static float f2 = 2.0f; + static int i1 = 10; + static int i2 = 20; + static float const *const self_a = &f1; + static float const *const self_b = &f2; + static int *const ret_a = &i1; + static int *const ret_b = &i2; + + void setUp(void) {} + void tearDown(void) {} + + :units: + - :pass: TRUE + :should: 'compile and pass when the correct argument is passed and return value matches' + :code: | + test() + { + foo_ExpectAndReturn(self_a, ret_a); + TEST_ASSERT_EQUAL_PTR(ret_a, exercise(self_a)); + } + + - :pass: FALSE + :should: 'fail when the wrong argument pointer is passed' + :code: | + test() + { + foo_ExpectAndReturn(self_a, ret_a); + exercise(self_b); + } + + - :pass: TRUE + :should: 'return the exact pointer provided to ExpectAndReturn' + :code: | + test() + { + foo_ExpectAndReturn(self_a, ret_b); + TEST_ASSERT_EQUAL_PTR(ret_b, exercise(self_a)); + } + + - :pass: FALSE + :should: 'fail when the returned pointer does not match what the test expects' + :code: | + test() + { + foo_ExpectAndReturn(self_a, ret_b); + TEST_ASSERT_EQUAL_PTR(ret_a, exercise(self_a)); + } + +... diff --git a/test/unit/cmock_generator_plugin_expect_a_test.rb b/test/unit/cmock_generator_plugin_expect_a_test.rb index eede693..8d12c2e 100644 --- a/test/unit/cmock_generator_plugin_expect_a_test.rb +++ b/test/unit/cmock_generator_plugin_expect_a_test.rb @@ -187,4 +187,98 @@ describe CMockGeneratorPluginExpect, "Verify CMockGeneratorPluginExpect Module W returned = @cmock_generator_plugin_expect.mock_verify(function) assert_equal(expected, returned) end + + it "preserve const-pointer ordering in typedef struct fields for arguments" do + function = { + :name => "Willow", + :args => [ + { :name => "ptr_to_const", :type => "const int*", :ptr? => true, :const? => true, :const_ptr? => false }, + { :name => "const_ptr", :type => "int*", :ptr? => true, :const? => false, :const_ptr? => true }, + { :name => "both_const", :type => "const int*", :ptr? => true, :const? => true, :const_ptr? => true }, + { :name => "plain_ptr", :type => "int*", :ptr? => true, :const? => false, :const_ptr? => false }, + ], + :return => test_return[:void] + } + # Struct fields use arg[:type] directly (no reconstruction via arg_type_with_const): + # - "const int*" preserved as "const int*" (pointer to const data) + # - "int*" (from int* const) stored as "int*" — the const_ptr? is intentionally omitted + # because a const struct field can never be written to, making the mock unworkable + # - "const int*" (from const int* const) similarly stored without the trailing const + expected = " const int* Expected_ptr_to_const;\n" + + " int* Expected_const_ptr;\n" + + " const int* Expected_both_const;\n" + + " int* Expected_plain_ptr;\n" + returned = @cmock_generator_plugin_expect.instance_typedefs(function) + assert_equal(expected, returned) + end + + it "preserve const-before-pointer in return typedef struct field" do + const_int_ptr_return = { :type => "const int*", :name => "cmock_to_return", :ptr? => true, + :const? => true, :const_ptr? => false, :void? => false, + :str => "const int* cmock_to_return" } + function = { :name => "Elm", :args => [], :return => const_int_ptr_return } + # ReturnVal uses return[:type] = "const int*" + expected = " const int* ReturnVal;\n" + returned = @cmock_generator_plugin_expect.instance_typedefs(function) + assert_equal(expected, returned) + end + + it "store const-pointer return value without trailing const in typedef struct field (for writability)" do + int_ptr_const_return = { :type => "int*", :name => "cmock_to_return", :ptr? => true, + :const? => false, :const_ptr? => true, :void? => false, + :str => "int* const cmock_to_return" } + function = { :name => "Elm", :args => [], :return => int_ptr_const_return } + # ReturnVal uses return[:type] = "int*"; the trailing const is intentionally dropped + # so that the struct field remains assignable + expected = " int* ReturnVal;\n" + returned = @cmock_generator_plugin_expect.instance_typedefs(function) + assert_equal(expected, returned) + end + + it "preserve const and pointer order in mock function declaration" do + int_ptr_const_return = { :type => "int*", :name => "cmock_to_return", :ptr? => true, + :const? => false, :const_ptr? => true, :void? => false, + :str => "int* const cmock_to_return" } + function = { + :name => "Cedar", + :args => [ + { :name => "p", :type => "const int*", :ptr? => true, :const? => true, :const_ptr? => false }, + { :name => "q", :type => "int*", :ptr? => true, :const? => false, :const_ptr? => true } + ], + :args_string => "const int* p, int* const q", + :args_call => "p, q", + :return => int_ptr_const_return + } + expected = "#define Cedar_Expect(p, q) TEST_FAIL_MESSAGE(\"Cedar requires _ExpectAndReturn\");\n" + + "#define Cedar_ExpectAndReturn(p, q, cmock_retval) Cedar_CMockExpectAndReturn(__LINE__, p, q, cmock_retval)\n" + + "void Cedar_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, const int* p, int* const q, int* const cmock_to_return);\n" + returned = @cmock_generator_plugin_expect.mock_function_declarations(function) + assert_equal(expected, returned) + end + + it "preserve const-before-pointer return type in mock function declaration for void-arg functions" do + const_int_ptr_return = { :type => "const int*", :name => "cmock_to_return", :ptr? => true, + :const? => true, :const_ptr? => false, :void? => false, + :str => "const int* cmock_to_return" } + function = { :name => "Oak", :args => [], :args_string => "void", :args_call => "", + :return => const_int_ptr_return } + expected = "#define Oak_Expect() TEST_FAIL_MESSAGE(\"Oak requires _ExpectAndReturn\");\n" + + "#define Oak_ExpectAndReturn(cmock_retval) Oak_CMockExpectAndReturn(__LINE__, cmock_retval)\n" + + "void Oak_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, const int* cmock_to_return);\n" + returned = @cmock_generator_plugin_expect.mock_function_declarations(function) + assert_equal(expected, returned) + end + + it "preserve const-after-pointer return type in mock function declaration for void-arg functions" do + int_ptr_const_return = { :type => "int*", :name => "cmock_to_return", :ptr? => true, + :const? => false, :const_ptr? => true, :void? => false, + :str => "int* const cmock_to_return" } + function = { :name => "Oak", :args => [], :args_string => "void", :args_call => "", + :return => int_ptr_const_return } + expected = "#define Oak_Expect() TEST_FAIL_MESSAGE(\"Oak requires _ExpectAndReturn\");\n" + + "#define Oak_ExpectAndReturn(cmock_retval) Oak_CMockExpectAndReturn(__LINE__, cmock_retval)\n" + + "void Oak_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, int* const cmock_to_return);\n" + returned = @cmock_generator_plugin_expect.mock_function_declarations(function) + assert_equal(expected, returned) + end end diff --git a/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb b/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb index 65b80e7..021eb5c 100644 --- a/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb +++ b/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb @@ -207,4 +207,74 @@ describe CMockGeneratorPluginReturnThruPtr, "Verify CMockGeneratorPluginReturnTh assert_equal(expected, returned) end + it "converts single pointer type to pointer-to-const via ptr_to_const" do + plugin = @cmock_generator_plugin_return_thru_ptr + assert_equal("int const*", plugin.ptr_to_const("int*")) + assert_equal("char const*", plugin.ptr_to_const("char*")) + assert_equal("uint8_t const*", plugin.ptr_to_const("uint8_t*")) + assert_equal("void const*", plugin.ptr_to_const("void*")) + assert_equal("MY_TYPE const*", plugin.ptr_to_const("MY_TYPE*")) + end + + it "converts double pointer type by making inner pointer const via ptr_to_const" do + plugin = @cmock_generator_plugin_return_thru_ptr + assert_equal("char* const*", plugin.ptr_to_const("char**")) + assert_equal("int* const*", plugin.ptr_to_const("int**")) + end + + it "includes int* const args (const pointer, mutable data) in typedef but excludes const int* args" do + # int* const: const_ptr?=true, const?=false → data is mutable, pointer is const + # The condition `!(arg[:const?])` checks whether the POINTED-TO data is const. + # const? is about the data, not the pointer itself, so int* const IS included. + const_ptr_func = { + :name => "Birch", + :args => [ + { :type => "int*", :name => "mutable_ptr", :ptr? => true, :const? => false, :const_ptr? => false }, + { :type => "int*", :name => "const_ptr", :ptr? => true, :const? => false, :const_ptr? => true }, + { :type => "const int*", :name => "ptr_to_const", :ptr? => true, :const? => true, :const_ptr? => false }, + ], + :return => test_return[:void] + } + + @utils.expect :ptr_or_str?, true, ["int*"] + @utils.expect :ptr_or_str?, true, ["int*"] + @utils.expect :ptr_or_str?, true, ["const int*"] + + # mutable_ptr and const_ptr are included; ptr_to_const is excluded (const?=true) + expected = " char ReturnThruPtr_mutable_ptr_Used;\n" + + " int const* ReturnThruPtr_mutable_ptr_Val;\n" + + " size_t ReturnThruPtr_mutable_ptr_Size;\n" + + " char ReturnThruPtr_const_ptr_Used;\n" + + " int const* ReturnThruPtr_const_ptr_Val;\n" + + " size_t ReturnThruPtr_const_ptr_Size;\n" + + returned = @cmock_generator_plugin_return_thru_ptr.instance_typedefs(const_ptr_func) + assert_equal(expected, returned) + end + + it "generates correct function signature for int* const args in mock interface" do + const_ptr_func = { + :name => "Birch", + :args => [ + { :type => "int*", :name => "const_ptr", :ptr? => true, :const? => false, :const_ptr? => true }, + ], + :return => test_return[:void] + } + + @utils.expect :ptr_or_str?, true, ["int*"] + + # ptr_to_const("int*") = "int const*", so the helper function takes int const* const_ptr + expected = + "#define Birch_ReturnThruPtr_const_ptr(const_ptr)" + + " Birch_CMockReturnMemThruPtr_const_ptr(__LINE__, const_ptr, sizeof(int))\n" + + "#define Birch_ReturnArrayThruPtr_const_ptr(const_ptr, cmock_len)" + + " Birch_CMockReturnMemThruPtr_const_ptr(__LINE__, const_ptr, (cmock_len * sizeof(*const_ptr)))\n" + + "#define Birch_ReturnMemThruPtr_const_ptr(const_ptr, cmock_size)" + + " Birch_CMockReturnMemThruPtr_const_ptr(__LINE__, const_ptr, (cmock_size))\n" + + "void Birch_CMockReturnMemThruPtr_const_ptr(UNITY_LINE_TYPE cmock_line, int const* const_ptr, size_t cmock_size);\n" + + returned = @cmock_generator_plugin_return_thru_ptr.mock_function_declarations(const_ptr_func) + assert_equal(expected, returned) + end + end diff --git a/test/unit/cmock_generator_utils_test.rb b/test/unit/cmock_generator_utils_test.rb index 36b8888..cec492f 100644 --- a/test/unit/cmock_generator_utils_test.rb +++ b/test/unit/cmock_generator_utils_test.rb @@ -508,4 +508,65 @@ describe CMockGeneratorUtils, "Verify CMockGeneratorUtils Module" do " }\n" assert_equal(expected, utils.code_verify_an_arg_expectation(function, arg)) end + + it 'correctly reconstruct type strings preserving const and pointer order' do + # non-pointer: no const + assert_equal("int", CMockGeneratorUtils.arg_type_with_const({:type => "int", :ptr? => false, :const? => false, :const_ptr? => false})) + # non-pointer: const (prepended) + assert_equal("const int", CMockGeneratorUtils.arg_type_with_const({:type => "int", :ptr? => false, :const? => true, :const_ptr? => false})) + + # pointer to mutable: no const + assert_equal("int*", CMockGeneratorUtils.arg_type_with_const({:type => "int*", :ptr? => true, :const? => false, :const_ptr? => false})) + + # pointer to const (const int*): const already in :type, no trailing const + assert_equal("const int*", CMockGeneratorUtils.arg_type_with_const({:type => "const int*", :ptr? => true, :const? => true, :const_ptr? => false})) + + # const pointer (int* const): :type has no const, const_ptr? appends " const" + assert_equal("int* const", CMockGeneratorUtils.arg_type_with_const({:type => "int*", :ptr? => true, :const? => false, :const_ptr? => true})) + + # const pointer to const (const int* const) + assert_equal("const int* const", CMockGeneratorUtils.arg_type_with_const({:type => "const int*", :ptr? => true, :const? => true, :const_ptr? => true})) + + # trailing-const form: int const* (same semantics as const int* but different spelling) + assert_equal("int const*", CMockGeneratorUtils.arg_type_with_const({:type => "int const*", :ptr? => true, :const? => true, :const_ptr? => false})) + + # trailing-const pointer to const: int const* const + assert_equal("int const* const", CMockGeneratorUtils.arg_type_with_const({:type => "int const*", :ptr? => true, :const? => true, :const_ptr? => true})) + + # custom type pointer + assert_equal("MY_TYPE*", CMockGeneratorUtils.arg_type_with_const({:type => "MY_TYPE*", :ptr? => true, :const? => false, :const_ptr? => false})) + assert_equal("const MY_TYPE", CMockGeneratorUtils.arg_type_with_const({:type => "MY_TYPE", :ptr? => false, :const? => true, :const_ptr? => false})) + + # double pointer: no const_ptr, so :type used as-is + assert_equal("int**", CMockGeneratorUtils.arg_type_with_const({:type => "int**", :ptr? => true, :const? => false, :const_ptr? => false})) + assert_equal("const int**", CMockGeneratorUtils.arg_type_with_const({:type => "const int**", :ptr? => true, :const? => true, :const_ptr? => false})) + # double pointer with const_ptr: appends " const" after last * + assert_equal("int** const", CMockGeneratorUtils.arg_type_with_const({:type => "int**", :ptr? => true, :const? => false, :const_ptr? => true})) + end + + it 'produce correct C declarations preserving const and pointer order' do + # const pointer to mutable int: int* const p + arg = {:type => "int*", :name => "p", :ptr? => true, :const? => false, :const_ptr? => true} + assert_equal("int* const p", CMockGeneratorUtils.arg_declaration(arg)) + + # pointer to const int: const int* p + arg = {:type => "const int*", :name => "p", :ptr? => true, :const? => true, :const_ptr? => false} + assert_equal("const int* p", CMockGeneratorUtils.arg_declaration(arg)) + + # const pointer to const int: const int* const p + arg = {:type => "const int*", :name => "p", :ptr? => true, :const? => true, :const_ptr? => true} + assert_equal("const int* const p", CMockGeneratorUtils.arg_declaration(arg)) + + # trailing-const form: int const* const p + arg = {:type => "int const*", :name => "p", :ptr? => true, :const? => true, :const_ptr? => true} + assert_equal("int const* const p", CMockGeneratorUtils.arg_declaration(arg)) + + # plain pointer: int* p + arg = {:type => "int*", :name => "p", :ptr? => true, :const? => false, :const_ptr? => false} + assert_equal("int* p", CMockGeneratorUtils.arg_declaration(arg)) + + # non-pointer const: const MY_TYPE v + arg = {:type => "MY_TYPE", :name => "v", :ptr? => false, :const? => true, :const_ptr? => false} + assert_equal("const MY_TYPE v", CMockGeneratorUtils.arg_declaration(arg)) + end end diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index b20247b..1d632f7 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -3003,4 +3003,140 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(true, src_len_arg[:array_size?], "src_len should be marked as array_size?") end + it "correctly parse const int* const return type (both const qualifiers)" do + sources = [ + "const int* const DoubleConst(void);\n", + "const int *const DoubleConst(void);\n", + ] + + # The trailing 'const' (const_ptr?) also lands in :modifier, matching the existing + # behavior seen for 'int* const' return types (see the test above for PorkRoast). + # The generated mock correctly uses return[:str] and function_return_type() which + # reconstruct the full "const int* const" from :type + const_ptr? without needing :modifier. + expected = [{ :var_arg => nil, + :name => "DoubleConst", + :unscoped_name => "DoubleConst", + :namespace => [], + :class => nil, + :return => { :type => "const int*", + :name => 'cmock_to_return', + :ptr? => true, + :const? => true, + :const_ptr? => true, + :str => "const int* const cmock_to_return", + :void? => false + }, + :modifier => "const", + :contains_ptr? => false, + :args => [], + :args_string => "void", + :args_call => "" + }] + + sources.each do |source| + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + end + + it "correctly parse int const* const return type (trailing-const form, both qualifiers)" do + sources = [ + "int const* const DoubleConst(void);\n", + "int const *const DoubleConst(void);\n", + ] + + expected = [{ :var_arg => nil, + :name => "DoubleConst", + :unscoped_name => "DoubleConst", + :namespace => [], + :class => nil, + :return => { :type => "int const*", + :name => 'cmock_to_return', + :ptr? => true, + :const? => true, + :const_ptr? => true, + :str => "int const* const cmock_to_return", + :void? => false + }, + :modifier => "const", + :contains_ptr? => false, + :args => [], + :args_string => "void", + :args_call => "" + }] + + sources.each do |source| + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + end + + it "correctly parse double-pointer argument types preserving const and pointer ordering" do + # Tests the full parse pipeline for double-pointer args (not just divine_ptr_and_const). + # const int** p → const? false (const is not before the final *), type preserves "const int**" + # int** const q → const_ptr? true, type is "int**" + # int* const* p → const? true (const* before the last *), type is "int* const*" + source = "void TriplePlay(const int** a, int** const b, int* const* c);\n" + + expected = [{ :var_arg => nil, + :name => "TriplePlay", + :unscoped_name => "TriplePlay", + :namespace => [], + :class => nil, + :return => { :type => "void", + :name => 'cmock_to_return', + :ptr? => false, + :const? => false, + :const_ptr? => false, + :str => "void cmock_to_return", + :void? => true + }, + :modifier => "", + :contains_ptr? => true, + :args => [ + { :type => "const int**", :name => "a", :ptr? => true, :string? => false, + :const? => false, :const_ptr? => false }, + { :type => "int**", :name => "b", :ptr? => true, :string? => false, + :const? => false, :const_ptr? => true }, + { :type => "int* const*", :name => "c", :ptr? => true, :string? => false, + :const? => true, :const_ptr? => false }, + ], + :args_string => "const int** a, int** const b, int* const* c", + :args_call => "a, b, c" + }] + + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + + it "correctly parse double-pointer argument types with all consts" do + # const int** const p → both const forms: type "const int**", const?=false, const_ptr?=true + # int* const* const q → both const forms: type "int* const*", const?=true, const_ptr?=true + source = "void AllConst(const int** const a, int* const* const b);\n" + + expected = [{ :var_arg => nil, + :name => "AllConst", + :unscoped_name => "AllConst", + :namespace => [], + :class => nil, + :return => { :type => "void", + :name => 'cmock_to_return', + :ptr? => false, + :const? => false, + :const_ptr? => false, + :str => "void cmock_to_return", + :void? => true + }, + :modifier => "", + :contains_ptr? => true, + :args => [ + { :type => "const int**", :name => "a", :ptr? => true, :string? => false, + :const? => false, :const_ptr? => true }, + { :type => "int* const*", :name => "b", :ptr? => true, :string? => false, + :const? => true, :const_ptr? => true }, + ], + :args_string => "const int** const a, int* const* const b", + :args_call => "a, b" + }] + + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + end