diff --git a/docs/CMock_Summary.md b/docs/CMock_Summary.md index f72a2f5..efd50a3 100644 --- a/docs/CMock_Summary.md +++ b/docs/CMock_Summary.md @@ -174,6 +174,23 @@ handle the call to a function. * `retval func(void)` => `void func_StopIgnore(void)` * `retval func(params)` => `void func_StopIgnore(void)` +IgnoreStateless: +---------------- + +This plugin is similar to the Ignore plugin, but the IgnoreAndReturn functions are +stateless. So the Ignored function will always return the last specified return value +and does not queue the return values as the IgnoreAndReturn of the default plugin will. + +To stop ignoring a function you can call StopIgnore or simply overwrite the Ignore +(resp. IgnoreAndReturn) with an Expect (resp. ExpectAndReturn). Note that calling +Ignore (resp IgnoreAndReturn) will clear your previous called Expect +(resp. ExpectAndReturn), so they are not restored after StopIgnore is called. + +You can use this plugin by using `:ignore_stateless` instead of `:ignore` in your +CMock configuration file. + +The generated functions are the same as **Ignore** and **StopIgnore** above. + Ignore Arg: ------------ @@ -440,6 +457,7 @@ from the defaults. We've tried to specify what the defaults are below. available currently: * `:ignore` + * `:ignore_stateless` * `:ignore_arg` * `:expect_any_args` * `:array` diff --git a/lib/cmock_generator_plugin_ignore_stateless.rb b/lib/cmock_generator_plugin_ignore_stateless.rb new file mode 100644 index 0000000..0551067 --- /dev/null +++ b/lib/cmock_generator_plugin_ignore_stateless.rb @@ -0,0 +1,85 @@ +# ========================================== +# CMock Project - Automatic Mock Generation for C +# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams +# [Released under MIT License. Please refer to license.txt for details] +# ========================================== + +class CMockGeneratorPluginIgnoreStateless + attr_reader :priority + attr_reader :config, :utils + + def initialize(config, utils) + @config = config + @utils = utils + @priority = 2 + end + + def instance_structure(function) + if function[:return][:void?] + " char #{function[:name]}_IgnoreBool;\n" + else + " char #{function[:name]}_IgnoreBool;\n #{function[:return][:type]} #{function[:name]}_FinalReturn;\n" + end + end + + def mock_function_declarations(function) + lines = if (function[:return][:void?]) + "#define #{function[:name]}_Ignore() #{function[:name]}_CMockIgnore()\n" + + "void #{function[:name]}_CMockIgnore(void);\n" + else + "#define #{function[:name]}_IgnoreAndReturn(cmock_retval) #{function[:name]}_CMockIgnoreAndReturn(cmock_retval)\n" + + "void #{function[:name]}_CMockIgnoreAndReturn(#{function[:return][:str]});\n"; + end + + # Add stop ignore function. it does not matter if there are any args + lines << "#define #{function[:name]}_StopIgnore() #{function[:name]}_CMockStopIgnore()\n" \ + "void #{function[:name]}_CMockStopIgnore(void);\n" + lines + end + + def mock_implementation_precheck(function) + lines = " if (Mock.#{function[:name]}_IgnoreBool)\n {\n" + lines << " UNITY_CLR_DETAILS();\n" + if function[:return][:void?] + lines << " return;\n }\n" + else + retval = function[:return].merge(:name => 'cmock_call_instance->ReturnVal') + lines << " if (cmock_call_instance == NULL)\n return Mock.#{function[:name]}_FinalReturn;\n" + lines << ' ' + @utils.code_assign_argument_quickly("Mock.#{function[:name]}_FinalReturn", retval) unless retval[:void?] + lines << " return cmock_call_instance->ReturnVal;\n }\n" + end + lines + end + + # this function is adjusted + def mock_interfaces(function) + lines = '' + lines << if function[:return][:void?] + "void #{function[:name]}_CMockIgnore(void)\n{\n" + else + "void #{function[:name]}_CMockIgnoreAndReturn(#{function[:return][:str]})\n{\n" + end + unless function[:return][:void?] + lines << " Mock.#{function[:name]}_CallInstance = CMOCK_GUTS_NONE;\n" + lines << " Mock.#{function[:name]}_FinalReturn = cmock_to_return;\n" + end + lines << " Mock.#{function[:name]}_IgnoreBool = (char)1;\n" + lines << "}\n\n" + + # Add stop ignore function. it does not matter if there are any args + lines << "void #{function[:name]}_CMockStopIgnore(void)\n{\n" + lines << " Mock.#{function[:name]}_IgnoreBool = (char)0;\n" + lines << "}\n\n" + + lines + end + + def mock_ignore(function) + " Mock.#{function[:name]}_IgnoreBool = (char)1;\n" + end + + def mock_verify(function) + func_name = function[:name] + " if (Mock.#{func_name}_IgnoreBool)\n call_instance = CMOCK_GUTS_NONE;\n" + end +end diff --git a/lib/cmock_generator_utils.rb b/lib/cmock_generator_utils.rb index 11225d6..7e1ce51 100644 --- a/lib/cmock_generator_utils.rb +++ b/lib/cmock_generator_utils.rb @@ -17,6 +17,7 @@ class CMockGeneratorUtils @return_thru_ptr = @config.plugins.include? :return_thru_ptr @ignore_arg = @config.plugins.include? :ignore_arg @ignore = @config.plugins.include? :ignore + @ignore_stateless = @config.plugins.include? :ignore_stateless @treat_as = @config.treat_as @helpers = helpers end @@ -52,7 +53,7 @@ class CMockGeneratorUtils lines << " UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringOutOfMemory);\n" lines << " memset(cmock_call_instance, 0, sizeof(*cmock_call_instance));\n" lines << " Mock.#{func_name}_CallInstance = CMock_Guts_MemChain(Mock.#{func_name}_CallInstance, cmock_guts_index);\n" - lines << " Mock.#{func_name}_IgnoreBool = (char)0;\n" if @ignore + lines << " Mock.#{func_name}_IgnoreBool = (char)0;\n" if @ignore || @ignore_stateless lines << " cmock_call_instance->LineNumber = cmock_line;\n" lines << " cmock_call_instance->CallOrder = ++GlobalExpectCount;\n" if @ordered && global_ordering_supported lines << " cmock_call_instance->ExceptionToThrow = CEXCEPTION_NONE;\n" if @cexception diff --git a/test/system/test_interactions/ignore_and_return_stateless.yml b/test/system/test_interactions/ignore_and_return_stateless.yml new file mode 100644 index 0000000..aefe4dd --- /dev/null +++ b/test/system/test_interactions/ignore_and_return_stateless.yml @@ -0,0 +1,325 @@ +--- +:cmock: + :plugins: + - 'ignore_stateless' + +:systest: + :types: | + + :mockable: | + int foo(int a); + void bar(int b); + + :source: + :header: | + int function(int a, int b, int c); + :code: | + int function(int a, int b, int c) + { + bar(b); + return foo(a) + foo(b) + foo(c); + } + + :tests: + :common: | + void setUp(void) {} + void tearDown(void) {} + + :units: + - :pass: TRUE + :should: 'successfully exercise simple ExpectAndReturn mock calls' + :code: | + test() + { + bar_Expect(2); + foo_ExpectAndReturn(1, 10); + foo_ExpectAndReturn(2, 20); + foo_ExpectAndReturn(3, 30); + TEST_ASSERT_EQUAL(60, function(1, 2, 3)); + } + + - :pass: TRUE + :should: 'ignore foo() calls' + :code: | + test() + { + bar_Expect(4); + foo_IgnoreAndReturn(40); + TEST_ASSERT_EQUAL(120, function(3, 4, 3)); + } + + - :pass: TRUE + :should: 'ignore the situation where foo() is not called even though we explicitly ignored it' + :code: | + test() + { + foo_IgnoreAndReturn(20); + //notice we do not call foo + } + + - :pass: TRUE + :should: 'ignore foo() calls and always return last item' + :code: | + test() + { + bar_Expect(4); + foo_IgnoreAndReturn(20); + foo_IgnoreAndReturn(30); + TEST_ASSERT_EQUAL(90, function(3, 4, 9)); + } + + - :pass: TRUE + :should: 'ignore bar() and foo() calls' + :code: | + test() + { + bar_Ignore(); + foo_IgnoreAndReturn(50); + TEST_ASSERT_EQUAL(150, function(0, 0, 0)); + } + + - :pass: TRUE + :should: 'multiple cycles of expects still pass when ignores enabled' + :code: | + test() + { + bar_Expect(2); + foo_ExpectAndReturn(1, 50); + foo_ExpectAndReturn(2, 60); + foo_ExpectAndReturn(3, 70); + TEST_ASSERT_EQUAL(180, function(1, 2, 3)); + + bar_Expect(5); + foo_ExpectAndReturn(4, 30); + foo_ExpectAndReturn(5, 80); + foo_ExpectAndReturn(6, 10); + TEST_ASSERT_EQUAL(120, function(4, 5, 6)); + + bar_Expect(8); + foo_ExpectAndReturn(7, 70); + foo_ExpectAndReturn(8, 20); + foo_ExpectAndReturn(9, 20); + TEST_ASSERT_EQUAL(110, function(7, 8, 9)); + } + + - :pass: FALSE + :should: 'multiple cycles of expects still fail when ignores enabled' + :code: | + test() + { + bar_Expect(2); + foo_ExpectAndReturn(1, 50); + foo_ExpectAndReturn(2, 60); + foo_ExpectAndReturn(3, 70); + TEST_ASSERT_EQUAL(180, function(1, 2, 3)); + + bar_Expect(5); + foo_ExpectAndReturn(4, 30); + foo_ExpectAndReturn(5, 80); + foo_ExpectAndReturn(6, 10); + TEST_ASSERT_EQUAL(120, function(4, 5, 6)); + + bar_Expect(8); + foo_ExpectAndReturn(7, 70); + foo_ExpectAndReturn(8, 20); + foo_ExpectAndReturn(9, 20); + TEST_ASSERT_EQUAL(110, function(0, 8, 9)); + } + + - :pass: FALSE + :should: 'With "fail_on_unexpected_calls" enabled, Expect/Ignore/... of bar is required and test fails.' + :code: | + test() + { + function(1, 2, 3); + } + + - :pass: TRUE + :should: 'we can override an ignore with an expect and pass' + :code: | + test() + { + bar_Ignore(); + + bar_Expect(2); + foo_ExpectAndReturn(1, 50); + foo_ExpectAndReturn(2, 60); + foo_ExpectAndReturn(3, 70); + TEST_ASSERT_EQUAL(180, function(1, 2, 3)); + + bar_Expect(5); + foo_ExpectAndReturn(4, 30); + foo_ExpectAndReturn(5, 80); + foo_ExpectAndReturn(6, 10); + TEST_ASSERT_EQUAL(120, function(4, 5, 6)); + + bar_Expect(8); + foo_ExpectAndReturn(7, 70); + foo_ExpectAndReturn(8, 20); + foo_ExpectAndReturn(9, 20); + TEST_ASSERT_EQUAL(110, function(7, 8, 9)); + } + + - :pass: FALSE + :should: 'we can override an ignore with an expect and fail' + :code: | + test() + { + bar_Ignore(); + + bar_Expect(2); + foo_ExpectAndReturn(1, 50); + foo_ExpectAndReturn(2, 60); + foo_ExpectAndReturn(3, 70); + TEST_ASSERT_EQUAL(180, function(1, 2, 3)); + + bar_Expect(5); + foo_ExpectAndReturn(4, 30); + foo_ExpectAndReturn(5, 80); + foo_ExpectAndReturn(6, 10); + TEST_ASSERT_EQUAL(120, function(4, 5, 6)); + + bar_Expect(9); + foo_ExpectAndReturn(7, 70); + foo_ExpectAndReturn(8, 20); + foo_ExpectAndReturn(9, 20); + TEST_ASSERT_EQUAL(110, function(7, 8, 9)); + } + + - :pass: TRUE + :should: 'we can override an ignore and return with an expect and pass' + :code: | + test() + { + bar_Ignore(); + foo_IgnoreAndReturn(30); + TEST_ASSERT_EQUAL(90, function(1, 2, 3)); + + bar_Expect(5); + foo_ExpectAndReturn(4, 30); + foo_ExpectAndReturn(5, 80); + foo_ExpectAndReturn(6, 10); + TEST_ASSERT_EQUAL(120, function(4, 5, 6)); + + bar_Expect(8); + foo_ExpectAndReturn(7, 70); + foo_ExpectAndReturn(8, 20); + foo_ExpectAndReturn(9, 20); + TEST_ASSERT_EQUAL(110, function(7, 8, 9)); + } + + - :pass: FALSE + :should: 'we can override an ignore and return with an expect and fail' + :code: | + test() + { + bar_Ignore(); + foo_IgnoreAndReturn(0); + TEST_ASSERT_EQUAL(0, function(1, 2, 3)); + + bar_Expect(5); + foo_ExpectAndReturn(4, 30); + foo_ExpectAndReturn(5, 80); + foo_ExpectAndReturn(6, 10); + TEST_ASSERT_EQUAL(120, function(4, 5, 6)); + + bar_Expect(9); + foo_ExpectAndReturn(7, 70); + foo_ExpectAndReturn(8, 20); + foo_ExpectAndReturn(9, 20); + TEST_ASSERT_EQUAL(110, function(7, 8, 9)); + } + + - :pass: TRUE + :should: 'we can override an expect with an ignore' + :code: | + test() + { + bar_Expect(5); + bar_Ignore(); + foo_ExpectAndReturn(1, 50); + foo_ExpectAndReturn(2, 60); + foo_ExpectAndReturn(3, 70); + TEST_ASSERT_EQUAL(180, function(1, 2, 3)); + } + + - :pass: FALSE + :should: 'we can override an expect with an ignore and return and fail after' + :code: | + test() + { + bar_Expect(5); + foo_ExpectAndReturn(4, 30); + foo_ExpectAndReturn(5, 50); + foo_IgnoreAndReturn(20); + TEST_ASSERT_EQUAL(100, function(4, 5, 6)); + + bar_Expect(5); + foo_ExpectAndReturn(9, 30); //THIS ONE WILL FAIL + foo_ExpectAndReturn(2, 80); + foo_ExpectAndReturn(3, 60); + TEST_ASSERT_EQUAL(170, function(1, 2, 3)); + } + + - :pass: TRUE + :should: 'we can override an expect with an ignore and return and only the ignore and return is acknowledged' + :code: | + test() + { + bar_Expect(5); + foo_ExpectAndReturn(2, 30); //NOTE THIS WOULD NORMALLY FAIL + foo_ExpectAndReturn(5, 50); + foo_IgnoreAndReturn(20); //BUT WE SAID WE NO LONGER CARE + TEST_ASSERT_EQUAL(60, function(4, 5, 6)); + } + +# StopIgnore + + - :pass: FALSE + :should: 'fail when function is called after ignore is stopped' + :code: | + test() + { + bar_Ignore(); + foo_IgnoreAndReturn(10); + function(0, 0, 0); + bar_StopIgnore(); + function(0, 0, 0); + } + + - :pass: FALSE + :should: 'delete expect after ignore is stopped' + :code: | + test() + { + bar_Ignore(); + foo_ExpectAndReturn(0, 40); + foo_ExpectAndReturn(0, 30); + foo_ExpectAndReturn(0, 40); + + foo_IgnoreAndReturn(20); + foo_StopIgnore(); + + TEST_ASSERT_EQUAL(110, function(0, 0, 0)); // THIS SHOULD FAIL + } + + - :pass: TRUE + :should: 'delete expected return values after ignore is stopped' + :code: | + test() + { + bar_Ignore(); + foo_ExpectAndReturn(0, 40); + foo_ExpectAndReturn(0, 30); + foo_ExpectAndReturn(0, 40); + + foo_IgnoreAndReturn(20); + foo_StopIgnore(); + + foo_ExpectAndReturn(0, 50); + foo_ExpectAndReturn(0, 30); + foo_ExpectAndReturn(0, 40); + TEST_ASSERT_EQUAL(120, function(0, 0, 0)); + } + +... diff --git a/test/unit/cmock_generator_plugin_ignore_stateless_test.rb b/test/unit/cmock_generator_plugin_ignore_stateless_test.rb new file mode 100644 index 0000000..b40d978 --- /dev/null +++ b/test/unit/cmock_generator_plugin_ignore_stateless_test.rb @@ -0,0 +1,116 @@ +# ========================================== +# CMock Project - Automatic Mock Generation for C +# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams +# [Released under MIT License. Please refer to license.txt for details] +# ========================================== + +require File.expand_path(File.dirname(__FILE__)) + "/../test_helper" +require File.expand_path(File.dirname(__FILE__)) + '/../../lib/cmock_generator_plugin_ignore_stateless' + +describe CMockGeneratorPluginIgnoreStateless, "Verify CMockGeneratorPluginIgnoreStateless Module" do + + before do + create_mocks :config, :utils + @config = create_stub(:respond_to? => true) + @cmock_generator_plugin_ignore_stateless = CMockGeneratorPluginIgnoreStateless.new(@config, @utils) + end + + after do + end + + it "have set up internal priority" do + assert_equal(2, @cmock_generator_plugin_ignore_stateless.priority) + end + + it "not have any additional include file requirements" do + assert(!@cmock_generator_plugin_ignore_stateless.respond_to?(:include_files)) + end + + it "add a required variable to the instance structure" do + function = {:name => "Grass", :args => [], :return => test_return[:void]} + expected = " char Grass_IgnoreBool;\n" + returned = @cmock_generator_plugin_ignore_stateless.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" + + "#define Mold_StopIgnore() Mold_CMockStopIgnore()\nvoid Mold_CMockStopIgnore(void);\n" + returned = @cmock_generator_plugin_ignore_stateless.mock_function_declarations(function) + assert_equal(expected, returned) + end + + it "handle function declarations for functions that returns something" do + function = {:name => "Fungus", :args_string => "void", :return => test_return[:string]} + expected = "#define Fungus_IgnoreAndReturn(cmock_retval) Fungus_CMockIgnoreAndReturn(cmock_retval)\n"+ + "void Fungus_CMockIgnoreAndReturn(const char* cmock_to_return);\n" + + "#define Fungus_StopIgnore() Fungus_CMockStopIgnore()\n"+ + "void Fungus_CMockStopIgnore(void);\n" + returned = @cmock_generator_plugin_ignore_stateless.mock_function_declarations(function) + assert_equal(expected, returned) + end + + it "add required code to implementation precheck with void function" do + function = {:name => "Mold", :args_string => "void", :return => test_return[:void]} + expected = [" if (Mock.Mold_IgnoreBool)\n", + " {\n", + " UNITY_CLR_DETAILS();\n", + " return;\n", + " }\n" + ].join + returned = @cmock_generator_plugin_ignore_stateless.mock_implementation_precheck(function) + assert_equal(expected, returned) + end + + it "add required code to implementation precheck with return functions" do + function = {:name => "Fungus", :args_string => "void", :return => test_return[:int]} + retval = test_return[:int].merge({ :name => "cmock_call_instance->ReturnVal"}) + @utils.expect :code_assign_argument_quickly, ' mock_retval_0', ["Mock.Fungus_FinalReturn", retval] + expected = [" if (Mock.Fungus_IgnoreBool)\n", + " {\n", + " UNITY_CLR_DETAILS();\n", + " if (cmock_call_instance == NULL)\n", + " return Mock.Fungus_FinalReturn;\n", + " mock_retval_0", + " return cmock_call_instance->ReturnVal;\n", + " }\n" + ].join + returned = @cmock_generator_plugin_ignore_stateless.mock_implementation_precheck(function) + assert_equal(expected, returned) + end + + it "add a new mock interface for ignoring when function had no return value" do + function = {:name => "Slime", :args => [], :args_string => "void", :return => test_return[:void]} + expected = ["void Slime_CMockIgnore(void)\n", + "{\n", + " Mock.Slime_IgnoreBool = (char)1;\n", + "}\n\n", + "void Slime_CMockStopIgnore(void)\n", + "{\n", + " Mock.Slime_IgnoreBool = (char)0;\n", + "}\n\n" + ].join + returned = @cmock_generator_plugin_ignore_stateless.mock_interfaces(function) + assert_equal(expected, returned) + end + + it "add a new mock interface for ignoring when function has return value" do + function = {:name => "Slime", :args => [], :args_string => "void", :return => test_return[:int]} + @utils.expect :code_add_base_expectation, "mock_return_1", ["Slime", false] + expected = ["void Slime_CMockIgnoreAndReturn(int cmock_to_return)\n", + "{\n", + " Mock.Slime_CallInstance = CMOCK_GUTS_NONE;\n", + " Mock.Slime_FinalReturn = cmock_to_return;\n", + " Mock.Slime_IgnoreBool = (char)1;\n", + "}\n\n", + "void Slime_CMockStopIgnore(void)\n", + "{\n", + " Mock.Slime_IgnoreBool = (char)0;\n", + "}\n\n" + ].join + returned = @cmock_generator_plugin_ignore_stateless.mock_interfaces(function) + 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 ab9df2a..b1f3268 100644 --- a/test/unit/cmock_generator_utils_test.rb +++ b/test/unit/cmock_generator_utils_test.rb @@ -20,17 +20,19 @@ describe CMockGeneratorUtils, "Verify CMockGeneratorUtils Module" do @config.expect :plugins, [] @config.expect :plugins, [] @config.expect :plugins, [] + @config.expect :plugins, [] @config.expect :treat_as, {'int' => 'INT','short' => 'INT16','long' => 'INT','char' => 'INT8','const char*' => 'STRING'} @cmock_generator_utils_simple = CMockGeneratorUtils.new(@config, {:unity_helper => @unity_helper}) @config.expect :when_ptr, :smart @config.expect :enforce_strict_ordering, true - @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore] - @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore] - @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore] - @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore] - @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore] - @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore] + @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore, :ignore_stateless] + @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore, :ignore_stateless] + @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore, :ignore_stateless] + @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore, :ignore_stateless] + @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore, :ignore_stateless] + @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore, :ignore_stateless] + @config.expect :plugins, [:array, :cexception, :return_thru_ptr, :ignore_arg, :ignore, :ignore_stateless] @config.expect :treat_as, {'int' => 'INT','short' => 'INT16','long' => 'INT','char' => 'INT8','uint32_t' => 'HEX32','const char*' => 'STRING'} @cmock_generator_utils_complex = CMockGeneratorUtils.new(@config, {:unity_helper => @unity_helper, :A=>1, :B=>2}) end