Add ability to monitor mock behaviors across all mocks and expectations (Implements #403)

This commit is contained in:
Mark VanderVoord
2026-06-30 13:31:49 -04:00
parent 125fcdef3c
commit 4b523b568e
25 changed files with 285 additions and 13 deletions
+2 -1
View File
@@ -56,7 +56,8 @@ class CMockConfig
# Compile-time assertion macro names to strip entirely to avoid being confused with function prototypes
# Common C11, BSD, and embedded RTOS variants are included by default. Add custom names as needed.
:ct_assert_patterns => ['ct_assert', '_?[Ss]tatic_[Aa]ssert', 'STATIC_ASSERT', 'BUILD_ASSERT', 'CTASSERT']
:ct_assert_patterns => ['ct_assert', '_?[Ss]tatic_[Aa]ssert', 'STATIC_ASSERT', 'BUILD_ASSERT', 'CTASSERT'],
:debug_output => false
}.freeze
def initialize(options = nil)
+2
View File
@@ -22,6 +22,7 @@ class CMockGenerator
@fail_on_unexpected_calls = @config.fail_on_unexpected_calls
@exclude_setjmp_h = @config.exclude_setjmp_h
@subdir = @config.subdir
@debug_output = @config.debug_output
@includes_h_pre_orig_header = ((@config.includes || []) + (@config.includes_h_pre_orig_header || [])).uniq.map { |h| h =~ /</ ? h : "\"#{h}\"" }
@includes_h_post_orig_header = (@config.includes_h_post_orig_header || []).map { |h| h =~ /</ ? h : "\"#{h}\"" }
@@ -344,6 +345,7 @@ class CMockGenerator
file << " UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;\n"
file << " CMOCK_#{function[:name]}_CALL_INSTANCE* cmock_call_instance;\n"
file << " UNITY_SET_DETAIL(CMockString_#{function[:name]});\n"
file << " TEST_MESSAGE(\"CMock: mock #{function[:name]} called\");\n" if @debug_output
file << " cmock_call_instance = (CMOCK_#{function[:name]}_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.#{function[:name]}_CallInstance);\n"
file << " Mock.#{function[:name]}_CallInstance = CMock_Guts_MemNext(Mock.#{function[:name]}_CallInstance);\n"
file << @plugins.run(:mock_precheck_return_thru_ptr, function)
+4
View File
@@ -15,6 +15,7 @@ class CMockGeneratorPluginCallback
@priority = 6
@include_count = @config.callback_include_count
@debug_output = @config.debug_output
end
def instance_structure(function)
@@ -72,13 +73,16 @@ class CMockGeneratorPluginCallback
has_ignore = @config.plugins.include? :ignore
lines = ''
lines << "void #{func_name}_AddCallback(CMOCK_#{func_name}_CALLBACK Callback)\n{\n"
lines << " TEST_MESSAGE(\"CMock: #{func_name}_AddCallback called\");\n" if @debug_output
lines << " Mock.#{func_name}_IgnoreBool = (char)0;\n" if has_ignore
lines << " Mock.#{func_name}_CallbackBool = (char)1;\n"
lines << " Mock.#{func_name}_CallbackCalls = 0;\n"
lines << " Mock.#{func_name}_CallbackFunctionPointer = Callback;\n}\n\n"
lines << "int #{func_name}_CallCount(void)\n{\n"
lines << " TEST_MESSAGE(\"CMock: #{func_name}_CallCount called\");\n" if @debug_output
lines << " return Mock.#{func_name}_CallbackCalls;\n}\n\n"
lines << "void #{func_name}_Stub(CMOCK_#{func_name}_CALLBACK Callback)\n{\n"
lines << " TEST_MESSAGE(\"CMock: #{func_name}_Stub called\");\n" if @debug_output
lines << " Mock.#{func_name}_IgnoreBool = (char)0;\n" if has_ignore
lines << " Mock.#{func_name}_CallbackBool = (char)0;\n"
lines << " Mock.#{func_name}_CallbackCalls = 0;\n"
+2
View File
@@ -17,6 +17,7 @@ class CMockGeneratorPluginExpect
@utils = utils
@unity_helper = @utils.helpers[:unity_helper]
@priority = 5
@debug_output = @config.debug_output
if @config.plugins.include? :expect_any_args
alias :mock_implementation :mock_implementation_might_check_args
@@ -92,6 +93,7 @@ class CMockGeneratorPluginExpect
else
"void #{func_name}_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, #{function[:args_string]}, #{function[:return][:str]})\n{\n"
end
lines << " TEST_MESSAGE(\"CMock: #{func_name}_#{function[:return][:void?] ? 'Expect' : 'ExpectAndReturn'} called\");\n" if @debug_output
lines << @utils.code_add_base_expectation(func_name)
lines << @utils.code_call_argument_loader(function)
lines << @utils.code_assign_argument_quickly('cmock_call_instance->ReturnVal', function[:return]) unless function[:return][:void?]
@@ -13,6 +13,7 @@ class CMockGeneratorPluginExpectAnyArgs
@error_stubs = @config.create_error_stubs
@utils = utils
@priority = 3
@debug_output = @config.debug_output
end
def instance_typedefs(_function)
@@ -43,6 +44,7 @@ class CMockGeneratorPluginExpectAnyArgs
else
"void #{function[:name]}_CMockExpectAnyArgsAndReturn(UNITY_LINE_TYPE cmock_line, #{function[:return][:str]})\n{\n"
end
lines << " TEST_MESSAGE(\"CMock: #{function[:name]}_#{function[:return][:void?] ? 'ExpectAnyArgs' : 'ExpectAnyArgsAndReturn'} called\");\n" if @debug_output
lines << @utils.code_add_base_expectation(function[:name], true)
unless function[:return][:void?]
lines << " cmock_call_instance->ReturnVal = cmock_to_return;\n"
+3
View File
@@ -13,6 +13,7 @@ class CMockGeneratorPluginIgnore
@error_stubs = @config.create_error_stubs
@utils = utils
@priority = 2
@debug_output = @config.debug_output
end
def instance_structure(function)
@@ -62,6 +63,7 @@ class CMockGeneratorPluginIgnore
else
"void #{function[:name]}_CMockIgnoreAndReturn(UNITY_LINE_TYPE cmock_line, #{function[:return][:str]})\n{\n"
end
lines << " TEST_MESSAGE(\"CMock: #{function[:name]}_#{function[:return][:void?] ? 'Ignore' : 'IgnoreAndReturn'} called\");\n" if @debug_output
unless function[:return][:void?]
lines << @utils.code_add_base_expectation(function[:name], false)
end
@@ -73,6 +75,7 @@ class CMockGeneratorPluginIgnore
# Add stop ignore function. it does not matter if there are any args
lines << "void #{function[:name]}_CMockStopIgnore(void)\n{\n"
lines << " TEST_MESSAGE(\"CMock: #{function[:name]}_StopIgnore called\");\n" if @debug_output
unless function[:return][:void?]
lines << " if(Mock.#{function[:name]}_IgnoreBool)\n"
lines << " Mock.#{function[:name]}_CallInstance = CMock_Guts_MemNext(Mock.#{function[:name]}_CallInstance);\n"
+3 -1
View File
@@ -9,9 +9,10 @@ class CMockGeneratorPluginIgnoreArg
attr_reader :priority
attr_accessor :utils
def initialize(_config, utils)
def initialize(config, utils)
@utils = utils
@priority = 10
@debug_output = config.debug_output
end
def instance_typedefs(function)
@@ -38,6 +39,7 @@ class CMockGeneratorPluginIgnoreArg
function[:args].each do |arg|
lines << "void #{func_name}_CMockIgnoreArg_#{arg[:name]}(UNITY_LINE_TYPE cmock_line)\n"
lines << "{\n"
lines << " TEST_MESSAGE(\"CMock: #{func_name}_IgnoreArg_#{arg[:name]} called\");\n" if @debug_output
lines << " CMOCK_#{func_name}_CALL_INSTANCE* cmock_call_instance = " \
"(CMOCK_#{func_name}_CALL_INSTANCE*)CMock_Guts_GetAddressFor(CMock_Guts_MemEndOfChain(Mock.#{func_name}_CallInstance));\n"
lines << " UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringIgnPreExp);\n"
@@ -13,6 +13,7 @@ class CMockGeneratorPluginIgnoreStateless
@error_stubs = @config.create_error_stubs
@utils = utils
@priority = 2
@debug_output = @config.debug_output
end
def instance_structure(function)
@@ -63,6 +64,7 @@ class CMockGeneratorPluginIgnoreStateless
else
"void #{function[:name]}_CMockIgnoreAndReturn(#{function[:return][:str]})\n{\n"
end
lines << " TEST_MESSAGE(\"CMock: #{function[:name]}_#{function[:return][:void?] ? 'Ignore' : 'IgnoreAndReturn'} called\");\n" if @debug_output
unless function[:return][:void?]
lines << " Mock.#{function[:name]}_CallInstance = CMOCK_GUTS_NONE;\n"
lines << " Mock.#{function[:name]}_FinalReturn = cmock_to_return;\n"
@@ -72,6 +74,7 @@ class CMockGeneratorPluginIgnoreStateless
# Add stop ignore function. it does not matter if there are any args
lines << "void #{function[:name]}_CMockStopIgnore(void)\n{\n"
lines << " TEST_MESSAGE(\"CMock: #{function[:name]}_StopIgnore called\");\n" if @debug_output
lines << " Mock.#{function[:name]}_IgnoreBool = (char)0;\n"
lines << "}\n\n"
@@ -13,6 +13,7 @@ class CMockGeneratorPluginReturnThruPtr
@utils = utils
@priority = 9
@config = config
@debug_output = @config.debug_output
plugins = @config.plugins
@ignore_used = plugins.include?(:ignore) || plugins.include?(:ignore_stateless)
end
@@ -100,6 +101,7 @@ class CMockGeneratorPluginReturnThruPtr
lines << "void #{func_name}_CMockReturnMemThruPtr_#{arg_name}(UNITY_LINE_TYPE cmock_line, #{ptr_to_const(arg[:type])} #{arg_name}, size_t cmock_size)\n"
lines << "{\n"
lines << " TEST_MESSAGE(\"CMock: #{func_name}_ReturnThruPtr_#{arg_name} called\");\n" if @debug_output
lines << " CMOCK_#{func_name}_CALL_INSTANCE* cmock_call_instance = " \
"(CMOCK_#{func_name}_CALL_INSTANCE*)CMock_Guts_GetAddressFor(CMock_Guts_MemEndOfChain(Mock.#{func_name}_CallInstance));\n"
if @ignore_used
+8 -1
View File
@@ -411,7 +411,9 @@ module RakefileHelpers
test_file = 'test_' + File.basename(test_case).ext(C_EXTENSION)
result_file = test_file.ext(RESULT_EXTENSION)
test_results = File.readlines(SYSTEST_BUILD_FILES_PATH + result_file).reject {|line| line.size < 10 }
all_results = File.readlines(SYSTEST_BUILD_FILES_PATH + result_file).reject {|line| line.size < 10 }
info_results = all_results.select {|line| line =~ /:INFO:/}
test_results = all_results.reject {|line| line =~ /:INFO:/}
tests.each_with_index do |test, index|
this_failed = case(test[:pass])
when :ignore
@@ -432,6 +434,11 @@ module RakefileHelpers
new_msg = "#{test_file}:test#{index+1}:should #{test[:should]}:should have output matching '#{test[:verify_error]}' but was '#{test_results[index]}'"
failure_messages << new_msg
report new_msg
elsif (test[:verify_message]) and not info_results.any? {|line| line =~ /test#{index+1}:INFO:.*#{test[:verify_message]}/}
total_failures += 1
new_msg = "#{test_file}:test#{index+1}:should #{test[:should]}:should have message matching '#{test[:verify_message]}' but none found in: #{info_results.select{|l| l =~ /test#{index+1}:/}.inspect}"
failure_messages << new_msg
report new_msg
else
report "#{test_file}:test#{index+1}:should #{test[:should]}:PASS"
end
@@ -0,0 +1,235 @@
# =========================================================================
# CMock - Automatic Mock Generation for C
# ThrowTheSwitch.org
# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================
---
:cmock:
:debug_output: true
:plugins:
- :ignore
- :callback
- :expect_any_args
- :return_thru_ptr
- :ignore_arg
:systest:
:types: |
:mockable: |
void foo(int a);
int bar(void);
int qux(int a);
void baz(int* p);
:source:
:header: |
void function(int a);
:code: |
void function(int a)
{
int temp = 0;
foo(a);
foo(bar());
qux(a);
baz(&temp);
}
:tests:
:common: |
void setUp(void) {}
void tearDown(void) {}
void my_foo_callback(int a, int cmock_num_calls) { (void)a; (void)cmock_num_calls; }
:units:
- :pass: TRUE
:should: 'output a debug message when foo_Expect is called'
:verify_message: 'CMock: foo_Expect called'
:code: |
test()
{
foo_Expect(1);
foo_Expect(42);
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(1);
}
- :pass: TRUE
:should: 'output a debug message when mock foo is called'
:verify_message: 'CMock: mock foo called'
:code: |
test()
{
foo_Expect(1);
foo_Expect(42);
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(1);
}
- :pass: TRUE
:should: 'output a debug message when bar_ExpectAndReturn is called'
:verify_message: 'CMock: bar_ExpectAndReturn called'
:code: |
test()
{
foo_Expect(1);
foo_Expect(42);
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(1);
}
- :pass: TRUE
:should: 'output a debug message when mock bar is called'
:verify_message: 'CMock: mock bar called'
:code: |
test()
{
foo_Expect(1);
foo_Expect(42);
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(1);
}
- :pass: TRUE
:should: 'output a debug message when foo_Ignore is called'
:verify_message: 'CMock: foo_Ignore called'
:code: |
test()
{
foo_Ignore();
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(99);
}
- :pass: TRUE
:should: 'output a debug message when bar_IgnoreAndReturn is called'
:verify_message: 'CMock: bar_IgnoreAndReturn called'
:code: |
test()
{
foo_Expect(1);
foo_Expect(42);
bar_IgnoreAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(1);
}
- :pass: TRUE
:should: 'output a debug message when foo_StopIgnore is called'
:verify_message: 'CMock: foo_StopIgnore called'
:code: |
test()
{
foo_Ignore();
foo_StopIgnore();
foo_Expect(1);
foo_Expect(42);
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(1);
}
- :pass: TRUE
:should: 'output a debug message when foo_ExpectAnyArgs is called'
:verify_message: 'CMock: foo_ExpectAnyArgs called'
:code: |
test()
{
foo_ExpectAnyArgs();
foo_ExpectAnyArgs();
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(1);
}
- :pass: TRUE
:should: 'output a debug message when qux_ExpectAnyArgsAndReturn is called'
:verify_message: 'CMock: qux_ExpectAnyArgsAndReturn called'
:code: |
test()
{
foo_Expect(1);
foo_Expect(42);
bar_ExpectAndReturn(42);
qux_ExpectAnyArgsAndReturn(0);
baz_Ignore();
function(1);
}
- :pass: TRUE
:should: 'output a debug message when foo_AddCallback is called'
:verify_message: 'CMock: foo_AddCallback called'
:code: |
test()
{
foo_AddCallback(my_foo_callback);
foo_Expect(1);
foo_Expect(42);
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(1);
}
- :pass: TRUE
:should: 'output a debug message when foo_Stub is called'
:verify_message: 'CMock: foo_Stub called'
:code: |
test()
{
foo_Stub(my_foo_callback);
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(1);
}
- :pass: TRUE
:should: 'output a debug message when baz_ReturnThruPtr_p is called'
:verify_message: 'CMock: baz_ReturnThruPtr_p called'
:code: |
test()
{
int val = 99;
foo_Expect(1);
foo_Expect(42);
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Expect(NULL);
baz_IgnoreArg_p();
baz_ReturnThruPtr_p(&val);
function(1);
}
- :pass: TRUE
:should: 'output a debug message when foo_IgnoreArg_a is called'
:verify_message: 'CMock: foo_IgnoreArg_a called'
:code: |
test()
{
foo_Expect(999);
foo_IgnoreArg_a();
foo_Expect(999);
foo_IgnoreArg_a();
bar_ExpectAndReturn(42);
qux_IgnoreAndReturn(0);
baz_Ignore();
function(1);
}
...
+2
View File
@@ -50,6 +50,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do
@config.expect :fail_on_unexpected_calls, true
@config.expect :treat_inlines, :exclude
@config.expect :exclude_setjmp_h, false
@config.expect :debug_output, false
@cmock_generator = CMockGenerator.new(@config, @file_writer, @utils, @plugins)
@cmock_generator.module_name = @module_name
@cmock_generator.module_ext = '.h'
@@ -71,6 +72,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do
@config.expect :fail_on_unexpected_calls, true
@config.expect :treat_inlines, :exclude
@config.expect :exclude_setjmp_h, false
@config.expect :debug_output, false
@cmock_generator_strict = CMockGenerator.new(@config, @file_writer, @utils, @plugins)
@test_project = {
@@ -12,7 +12,7 @@ describe CMockGeneratorPluginExpectAnyArgs, "Verify Generation Of Mock Function
before do
create_mocks :config, :utils
@config = create_stub(:respond_to? => true, :create_error_stubs => false)
@config = create_stub(:respond_to? => true, :create_error_stubs => false, :debug_output => false)
@cmock_generator_plugin_expect_any_args = CMockGeneratorPluginExpectAnyArgs.new(@config, @utils)
end
@@ -18,7 +18,8 @@ describe CMockGeneratorPluginExpect, "Verify Generation Of Mock Function Declara
:enforce_strict_ordering => false,
:respond_to? => true,
:create_error_stubs => false,
:plugins => [ :expect ] )
:plugins => [ :expect ],
:debug_output => false )
@utils.expect :helpers, {}
@cmock_generator_plugin_expect = CMockGeneratorPluginExpect.new(@config, @utils)
@@ -12,7 +12,7 @@ describe CMockGeneratorPluginIgnoreStateless, "Verify Generation Of Mock Functio
before do
create_mocks :config, :utils
@config = create_stub(:respond_to? => true, :create_error_stubs => false)
@config = create_stub(:respond_to? => true, :create_error_stubs => false, :debug_output => false)
@cmock_generator_plugin_ignore_stateless = CMockGeneratorPluginIgnoreStateless.new(@config, @utils)
end
@@ -12,7 +12,7 @@ describe CMockGeneratorPluginIgnore, "Verify Generation Of Mock Function Declara
before do
create_mocks :config, :utils
@config = create_stub(:respond_to? => true, :create_error_stubs => false)
@config = create_stub(:respond_to? => true, :create_error_stubs => false, :debug_output => false)
@cmock_generator_plugin_ignore = CMockGeneratorPluginIgnore.new(@config, @utils)
end
@@ -16,6 +16,7 @@ describe CMockGeneratorPluginCallback, "Verify CMockGeneratorPluginCallback Modu
@config.expect :callback_include_count, true
@config.expect :callback_after_arg_check, false
@config.expect :plugins, [:ignore]
@config.expect :debug_output, false
@cmock_generator_plugin_callback = CMockGeneratorPluginCallback.new(@config, @utils)
end
@@ -18,7 +18,8 @@ describe CMockGeneratorPluginExpect, "Verify CMockGeneratorPluginExpect Module W
:enforce_strict_ordering => false,
:respond_to? => true,
:create_error_stubs => true,
:plugins => [ :expect ] )
:plugins => [ :expect ],
:debug_output => false )
@utils.expect :helpers, {}
@cmock_generator_plugin_expect = CMockGeneratorPluginExpect.new(@config, @utils)
@@ -12,7 +12,7 @@ describe CMockGeneratorPluginExpectAnyArgs, "Verify CMockGeneratorPluginExpectAn
before do
create_mocks :config, :utils
@config = create_stub(:respond_to? => true, :create_error_stubs => true)
@config = create_stub(:respond_to? => true, :create_error_stubs => true, :debug_output => false)
@cmock_generator_plugin_expect_any_args = CMockGeneratorPluginExpectAnyArgs.new(@config, @utils)
end
@@ -18,7 +18,8 @@ describe CMockGeneratorPluginExpect, "Verify CMockGeneratorPluginExpect Module w
:enforce_strict_ordering => true,
:respond_to? => true,
:create_error_stubs => true,
:plugins => [ :expect, :expect_any_args ] )
:plugins => [ :expect, :expect_any_args ],
:debug_output => false )
@utils.expect :helpers, {}
@cmock_generator_plugin_expect = CMockGeneratorPluginExpect.new(@config, @utils)
@@ -35,6 +35,7 @@ describe CMockGeneratorPluginIgnoreArg, "Verify CMockGeneratorPluginIgnoreArg Mo
:contains_ptr? => true }
#no strict ordering
@config.expect :debug_output, false
@cmock_generator_plugin_ignore_arg = CMockGeneratorPluginIgnoreArg.new(@config, @utils)
end
@@ -12,7 +12,7 @@ describe CMockGeneratorPluginIgnoreStateless, "Verify CMockGeneratorPluginIgnore
before do
create_mocks :config, :utils
@config = create_stub(:respond_to? => true, :create_error_stubs => true)
@config = create_stub(:respond_to? => true, :create_error_stubs => true, :debug_output => false)
@cmock_generator_plugin_ignore_stateless = CMockGeneratorPluginIgnoreStateless.new(@config, @utils)
end
@@ -12,7 +12,7 @@ describe CMockGeneratorPluginIgnore, "Verify CMockGeneratorPluginIgnore Module"
before do
create_mocks :config, :utils
@config = create_stub(:respond_to? => true, :create_error_stubs => true)
@config = create_stub(:respond_to? => true, :create_error_stubs => true, :debug_output => false)
@cmock_generator_plugin_ignore = CMockGeneratorPluginIgnore.new(@config, @utils)
end
@@ -69,6 +69,7 @@ describe CMockGeneratorPluginReturnThruPtr, "Verify CMockGeneratorPluginReturnTh
#no strict ordering
@config.expect :plugins, []
@config.expect :debug_output, false
@cmock_generator_plugin_return_thru_ptr = CMockGeneratorPluginReturnThruPtr.new(@config, @utils)
end
+2 -1
View File
@@ -21,7 +21,8 @@ describe CMockPluginManager, "Verify CMockPluginManager Module" do
:enforce_strict_ordering => false,
:ignore => :args_and_calls,
:exclude_setjmp_h => false,
:create_error_stubs => true
:create_error_stubs => true,
:debug_output => false
)
def @config.plugins