Verify that our handling of const and pointers now preserve order properly (Verifies #485)

This commit is contained in:
Mark VanderVoord
2026-06-26 11:14:10 -04:00
parent 5f74197056
commit d20d18e75c
5 changed files with 441 additions and 0 deletions
@@ -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));
}
...
@@ -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
@@ -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
+61
View File
@@ -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
+136
View File
@@ -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