From a27ec062b1d4bcccca1d193cc004abf26f7c3887 Mon Sep 17 00:00:00 2001 From: RAMZI ELKHATER Date: Fri, 19 Aug 2022 11:18:03 -0700 Subject: [PATCH 1/4] Adding a preamble to mocked function name The change in this patch modifies fff macro generation script to support a CLI option --preamble that can be used to set a preamble for generated mock function names. On some embedded systems if we want to perform testing with HW in the loop, there are system functions or driver functions that cannot be mocked safely. For example the SW running on a micro-controller may need to maintain communication with another subsystem, replacing the functions that do this with mocks will put the system in a bad state. To be able to mock such functions and keep the system in a good state we prepend a preamble to the mock function name and inside the mock function do intrumentation and then call the real function to keep the system in a good state. We can leverage the 'custom_fake' field created by fff to call the real function. For example, we can name the mock of function XYZ() as __wrap_XYZ() and use the GNU linker option -Wl,--wrap XYZ to re-route all references to XYZ() to __wrap_XYZ() (see https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_3.html). Now, inside __wrap_XYZ() we can invoke XYZ() in addition to instrumentation. We can leverage the 'custom_fake' field created by fff to call the real version of XYZ(), this will be named as __real_XYZ(). Note, this is just an example the same can be acheived by custom tooling and not using GNU linkers --wrap flag. Signed-off-by: Ramzi El-khater --- fakegen.rb | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/fakegen.rb b/fakegen.rb index d937bd3..1bfd8b3 100644 --- a/fakegen.rb +++ b/fakegen.rb @@ -35,7 +35,7 @@ def output_constants putd "#define FFF_GCC_FUNCTION_ATTRIBUTES" } putd "#endif" - + end @@ -269,7 +269,7 @@ def indent popd end -def output_macro(arg_count, has_varargs, has_calling_conventions, is_value_function) +def output_macro(arg_count, has_varargs, has_calling_conventions, is_value_function, preamble) vararg_name = has_varargs ? "_VARARG" : "" fake_macro_name = is_value_function ? "FAKE_VALUE_FUNC#{arg_count}#{vararg_name}" : "FAKE_VOID_FUNC#{arg_count}#{vararg_name}" @@ -281,16 +281,16 @@ def output_macro(arg_count, has_varargs, has_calling_conventions, is_value_funct puts output_macro_header(declare_macro_name, saved_arg_count, has_varargs, has_calling_conventions, return_type) indent { - output_variables(saved_arg_count, has_varargs, has_calling_conventions, is_value_function) + output_variables(saved_arg_count, has_varargs, has_calling_conventions, is_value_function, preamble) } puts output_macro_header(define_macro_name, saved_arg_count, has_varargs, has_calling_conventions, return_type) indent { putd_backslash "FUNCNAME##_Fake FUNCNAME##_fake;" - putd_backslash function_signature(saved_arg_count, has_varargs, has_calling_conventions, is_value_function) + "{" + putd_backslash function_signature(saved_arg_count, has_varargs, has_calling_conventions, is_value_function, preamble) + "{" indent { - output_function_body(saved_arg_count, has_varargs, is_value_function) + output_function_body(saved_arg_count, has_varargs, is_value_function, preamble) } putd_backslash "}" putd_backslash "DEFINE_RESET_FUNCTION(FUNCNAME)" @@ -334,7 +334,7 @@ def macro_signature_for(macro_name, arg_count, has_varargs, has_calling_conventi parameter_list end -def output_variables(arg_count, has_varargs, has_calling_conventions, is_value_function) +def output_variables(arg_count, has_varargs, has_calling_conventions, is_value_function, preamble) in_struct{ arg_count.times { |argN| putd_backslash "DECLARE_ARG(ARG#{argN}_TYPE, #{argN}, FUNCNAME)" @@ -348,7 +348,7 @@ def output_variables(arg_count, has_varargs, has_calling_conventions, is_value_f } putd_backslash "extern FUNCNAME##_Fake FUNCNAME##_fake;" putd_backslash "void FUNCNAME##_reset(void);" - putd_backslash function_signature(arg_count, has_varargs, has_calling_conventions, is_value_function) + ";" + putd_backslash function_signature(arg_count, has_varargs, has_calling_conventions, is_value_function, preamble) + ";" end #example: ARG0_TYPE arg0, ARG1_TYPE arg1 @@ -390,15 +390,15 @@ def output_custom_function_array(arg_count, has_varargs, has_calling_conventions # example: RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1) # OR # RETURN_TYPE CALLING_CONVENTION FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1) -def function_signature(arg_count, has_varargs, has_calling_conventions, is_value_function) +def function_signature(arg_count, has_varargs, has_calling_conventions, is_value_function, preamble) return_type = is_value_function ? "RETURN_TYPE" : "void" varargs = has_varargs ? ", ..." : "" calling_conventions = has_calling_conventions ? - "#{return_type} FFF_GCC_FUNCTION_ATTRIBUTES CALLING_CONVENTION FUNCNAME(#{arg_val_list(arg_count)}#{varargs})" : - "#{return_type} FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(#{arg_val_list(arg_count)}#{varargs})" + "#{return_type} FFF_GCC_FUNCTION_ATTRIBUTES CALLING_CONVENTION #{preamble}FUNCNAME(#{arg_val_list(arg_count)}#{varargs})" : + "#{return_type} FFF_GCC_FUNCTION_ATTRIBUTES #{preamble}FUNCNAME(#{arg_val_list(arg_count)}#{varargs})" end -def output_function_body(arg_count, has_varargs, is_value_function) +def output_function_body(arg_count, has_varargs, is_value_function, preamble) arg_count.times { |i| putd_backslash "SAVE_ARG(FUNCNAME, #{i});" } putd_backslash "if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){" indent { @@ -411,7 +411,7 @@ def output_function_body(arg_count, has_varargs, is_value_function) } putd_backslash "}" putd_backslash "INCREMENT_CALL_COUNT(FUNCNAME);" - putd_backslash "REGISTER_CALL(FUNCNAME);" + putd_backslash "REGISTER_CALL(#{preamble}FUNCNAME);" if has_varargs return_type = is_value_function ? "return " : "" @@ -662,17 +662,29 @@ def help end help { + args = ARGV + preamble = "" + while !args.empty? + current_arg, *args = args + if current_arg == "--with-calling-conventions" or current_arg == "-wcc" + has_calling_conventions = true + elsif current_arg == "--preamble" or current_arg == "-p" + preamble, *args = args + preamble = "#{preamble}##" + end + end + # Determine if we should generate with support for calling conventions - has_calling_conventions = true if (ARGV[0] == "--with-calling-conventions" or ARGV[0] == "-wcc") + #has_calling_conventions = true if (ARGV[0] == "--with-calling-conventions" or ARGV[0] == "-wcc") # lets generate!! output_c_and_cpp(has_calling_conventions) { define_fff_globals # Create fake generators for 0..MAX_ARGS num_fake_generators = $MAX_ARGS + 1 - num_fake_generators.times {|arg_count| output_macro(arg_count, false, has_calling_conventions, false)} - num_fake_generators.times {|arg_count| output_macro(arg_count, false, has_calling_conventions, true)} + num_fake_generators.times {|arg_count| output_macro(arg_count, false, has_calling_conventions, false, preamble)} + num_fake_generators.times {|arg_count| output_macro(arg_count, false, has_calling_conventions, true, preamble)} # generate the varargs variants - (2..$MAX_ARGS).each {|arg_count| output_macro(arg_count, true, has_calling_conventions, false)} - (2..$MAX_ARGS).each {|arg_count| output_macro(arg_count, true, has_calling_conventions, true)} + (2..$MAX_ARGS).each {|arg_count| output_macro(arg_count, true, has_calling_conventions, false, preamble)} + (2..$MAX_ARGS).each {|arg_count| output_macro(arg_count, true, has_calling_conventions, true, preamble)} } } From dd13eb61e43b24399efc82b0d5d70fbbb5184e9f Mon Sep 17 00:00:00 2001 From: RAMZI ELKHATER Date: Mon, 22 Aug 2022 19:21:52 -0700 Subject: [PATCH 2/4] Add unit-test for --preamble cli argument Update CMake file to generate "fff_preamble.h" using the cli argument "--preamble __wrap_" and add a test-case that invokes the generated fake function that is now named as "__wrap_voidfunc1()" and verifies the proper call_count gets incremented. Signed-off-by: Ramzi El-khater --- test/CMakeLists.txt | 25 ++++++++++++++++++++++--- test/fff_test_preamble.c | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 test/fff_test_preamble.c diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0aa6679..d288552 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -34,6 +34,25 @@ add_executable(cpp_global_test ) target_link_libraries(cpp_global_test PRIVATE gtest fff) +# genertae fff_preamble.h so we can test --preamble command +add_custom_command( + OUTPUT + ${CMAKE_CURRENT_LIST_DIR}/fff_preamble.h + COMMAND + ruby ${CMAKE_CURRENT_LIST_DIR}/../fakegen.rb --preamble __wrap_ >> ${CMAKE_CURRENT_LIST_DIR}/fff_preamble.h + DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/../fakegen.rb + ${CMAKE_CURRENT_LIST_DIR}/../LICENSE +) +add_custom_target(fff_preamble_h DEPENDS ${CMAKE_CURRENT_LIST_DIR}/fff_preamble.h) + +# Create the test executable to test fff_preamble.h +add_executable(c_preamble_test + fff_preamble.h + fff_test_preamble.c + ${COMMON_FILE_LIST} +) + # Add the tests for ctest add_test( NAME c_test @@ -51,6 +70,6 @@ add_test( ) add_test( - NAME cpp_global_test - COMMAND $ -) \ No newline at end of file + NAME c_preamble_test + COMMAND $ +) diff --git a/test/fff_test_preamble.c b/test/fff_test_preamble.c new file mode 100644 index 0000000..3a27972 --- /dev/null +++ b/test/fff_test_preamble.c @@ -0,0 +1,36 @@ +#include "fff_preamble.h" +#include "c_test_framework.h" + +FAKE_VOID_FUNC(voidfunc1, int); + +void setup() +{ + RESET_FAKE(voidfunc1); + FFF_RESET_HISTORY(); +} + +TEST_F(FFFTestSuite, fake_function_with_preamble_is_callable) +{ + // note the test case assumes the preamble is "__wrap_" + __wrap_voidfunc1(10); + ASSERT_EQ(voidfunc1_fake.call_count, 1); +} + +DEFINE_FFF_GLOBALS; +int main() +{ + setbuf(stdout, NULL); + fprintf(stdout, "-------------\n"); + fprintf(stdout, "Running Tests\n"); + fprintf(stdout, "-------------\n\n"); + fflush(0); + + /* Run tests */ + RUN_TEST(FFFTestSuite, fake_function_with_preamble_is_callable); + + printf("\n-------------\n"); + printf("Complete\n"); + printf("-------------\n\n"); + + return 0; +} From e34fbb060c2647f8dd54be709a1303b67654e9a5 Mon Sep 17 00:00:00 2001 From: RAMZI ELKHATER Date: Mon, 22 Aug 2022 19:39:39 -0700 Subject: [PATCH 3/4] Fix: accidently removed cpp_global_test in previous commit Signed-off-by: Ramzi El-khater --- test/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d288552..4259b86 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -69,6 +69,11 @@ add_test( COMMAND $ ) +add_test( + NAME cpp_global_test + COMMAND $ +) + add_test( NAME c_preamble_test COMMAND $ From 907fb639e2cc388d0543ddcce5eff3aa2eaae6ce Mon Sep 17 00:00:00 2001 From: RAMZI ELKHATER Date: Wed, 24 Aug 2022 15:06:13 -0700 Subject: [PATCH 4/4] Add documentation to document the --preamble argument Signed-off-by: Ramzi El-khater --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 3255e6a..ecc717a 100644 --- a/README.md +++ b/README.md @@ -627,6 +627,21 @@ You can mark all fakes with the weak attribute like so: See the example project that demonstrates the above approach: _./examples/weak_linking_. +## Specifying a preamble in the mock funtion name + +You can make fff add a preamble to the mock function name by generating fff.h as below +``` +ruby fakegen.rb --preamble bar_ +``` +This will cause the mock of function foo() to be named as bar_foo() for every mocked function foo(). + +This is useful if one needs to build with the real function included. Note that references to the real function will need to change to refer to the new mock function name, this can be done with the help of linkers. + +One example is below, +- Use method described here to name function foo() as __wrap_foo() +- use the GNU linker option -Wl,--wrap foo to re-route all references to foo() to __wrap_foo() +- Set 'foo_fake.custom_fake' field to point to the real version of foo(), this will now be named as __real_foo() + ## Find Out More Look under the examples directory for full length examples in both C and C++.