From 6f3fbb34d504d9951a4f405d7bbf53ae4be53f30 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Mon, 3 Jun 2013 16:42:20 +0200 Subject: [PATCH] example: wrap trick Adds a new example that illustrates the gcc wrap trick. --- example/CMakeLists.txt | 3 + example/chef_wrap/CMakeLists.txt | 19 ++++ example/chef_wrap/chef.c | 54 +++++++++++ example/chef_wrap/chef.h | 19 ++++ example/chef_wrap/waiter_test_wrap.c | 170 +++++++++++++++++++++++++++++++++++ example/chef_wrap/waiter_test_wrap.h | 2 + 6 files changed, 267 insertions(+) create mode 100644 example/chef_wrap/CMakeLists.txt create mode 100644 example/chef_wrap/chef.c create mode 100644 example/chef_wrap/chef.h create mode 100644 example/chef_wrap/waiter_test_wrap.c create mode 100644 example/chef_wrap/waiter_test_wrap.h diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index a060de1..d03c40a 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -68,6 +68,9 @@ set_tests_properties( "\\[ FAILED \\] 2 test" ) +if (${CMAKE_C_COMPILER_ID} MATCHES "(GNU|Clang)") + add_subdirectory(chef_wrap) +endif() add_executable(run_tests run_tests.c) target_link_libraries(run_tests ${CMOCKA_SHARED_LIBRARY}) diff --git a/example/chef_wrap/CMakeLists.txt b/example/chef_wrap/CMakeLists.txt new file mode 100644 index 0000000..b6bd6b5 --- /dev/null +++ b/example/chef_wrap/CMakeLists.txt @@ -0,0 +1,19 @@ +project(cmocka-wrap-examples C CXX) + +include_directories( + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMOCKA_PUBLIC_INCLUDE_DIRS} +) + +add_definitions(-DUNIT_TESTING=1) + +add_executable(waiter_test_wrap waiter_test_wrap.c chef.c) +target_link_libraries(waiter_test_wrap ${CMOCKA_SHARED_LIBRARY}) + +add_test(waiter_test_wrap ${CMAKE_CURRENT_BINARY_DIR}/waiter_test_wrap) + +set_target_properties(waiter_test_wrap + PROPERTIES + LINK_FLAGS "-Wl,--wrap=chef_cook" +) diff --git a/example/chef_wrap/chef.c b/example/chef_wrap/chef.c new file mode 100644 index 0000000..1429cde --- /dev/null +++ b/example/chef_wrap/chef.c @@ -0,0 +1,54 @@ +/* + * Copyright 2013 (c) Andreas Schneider + * Jakub Hrozek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chef.h" + + +/* This is the real chef, just not implemented yet, currently it always + * returns ENOSYS + */ +int chef_cook(const char *order, char **dish_out) +{ + if (order == NULL || dish_out == NULL) return EINVAL; + + return -ENOSYS; +} + +/* Print chef return codes as string */ +const char *chef_strerror(int error) +{ + switch (error) { + case 0: + return "Success"; + case -1: + return "Unknown dish"; + case -2: + return "Not enough ingredients for the dish"; + } + + return "Unknown error!"; +} + diff --git a/example/chef_wrap/chef.h b/example/chef_wrap/chef.h new file mode 100644 index 0000000..c1a01c7 --- /dev/null +++ b/example/chef_wrap/chef.h @@ -0,0 +1,19 @@ +/* + * Copyright 2013 (c) Andreas Schneider + * Jakub Hrozek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +int chef_cook(const char *order, char **dish_out); +const char *chef_strerror(int error); diff --git a/example/chef_wrap/waiter_test_wrap.c b/example/chef_wrap/waiter_test_wrap.c new file mode 100644 index 0000000..01afa15 --- /dev/null +++ b/example/chef_wrap/waiter_test_wrap.c @@ -0,0 +1,170 @@ +/* + * Copyright 2013 (c) Andreas Schneider + * Jakub Hrozek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "waiter_test_wrap.h" +#include "chef.h" + +/* + * This is a mocked Chef object. A real Chef would look if he knows + * the dish in some kind of internal database and check his storage for + * ingredients. This chef simply retrieves this information from the test + * that is calling him. + * + * This object is also wrapped - if any code links with this file and is + * compiled with linker option --wrap chef_cook, any calls of that code to + * chef_cook will end up calling __wrap_chef_cook. + * + * If for any reason the wrapped function wanted to call the real chef_cook() + * function, it could do so by calling the special symbol __real_chef_cook(). + * + * Please note that when setting return codes for the chef_cook function, we + * use this wrapper as a parameter for the will_return() macro, not the + * real function. + * + * A chef object would return: + * 0 - cooking dish went fine + * -1 - unknown dish + * -2 - ran out of ingredients for the dish + * any other error code -- unexpected error while cooking + * + * The return codes should be consistent between the real and mocked objects. + */ +int __wrap_chef_cook(const char *order, char **dish_out) +{ + bool has_ingredients; + bool knows_dish; + char *dish; + + check_expected(order); + + knows_dish = (bool) mock(); + if (knows_dish == false) { + return -1; + } + + has_ingredients = (bool) mock(); + if (has_ingredients == false) { + return -2; + } + + dish = (char *) mock(); + *dish_out = strdup(dish); + if (*dish_out == NULL) return ENOMEM; + + return mock(); +} + +/* Waiter return codes: + * 0 - success + * -1 - kitchen failed + * -2 - kitchen succeeded, but cooked a different food + */ +static int waiter_process(const char *order, char **dish) +{ + int rv; + + rv = chef_cook(order, dish); + if (rv != 0) { + fprintf(stderr, "Chef couldn't cook %s: %s\n", + order, chef_strerror(rv)); + return -1; + } + + /* Check if we received the dish we wanted from the kitchen */ + if (strcmp(order, *dish) != 0) { + *dish = NULL; + return -2; + } + + return 0; +} + +static void test_order_hotdog(void **state) +{ + (void) state; /* unused */ + + int rv; + char *dish; + + /* We expect the chef to receive an order for a hotdog */ + expect_string(__wrap_chef_cook, order, "hotdog"); + /* And we tell the test chef that ke knows how to cook a hotdog + * and has the ingredients + */ + will_return(__wrap_chef_cook, true); + will_return(__wrap_chef_cook, true); + /* The result will be a hotdog and the cooking process will succeed */ + will_return(__wrap_chef_cook, "hotdog"); + will_return(__wrap_chef_cook, 0); + + /* Test the waiter */ + rv = waiter_process("hotdog", &dish); + + /* We expect the cook to succeed cooking the hotdog */ + assert_int_equal(rv, 0); + /* And actually receive one */ + assert_string_equal(dish, "hotdog"); +} + +static void test_bad_dish(void **state) +{ + (void) state; /* unused */ + + int rv; + char *dish; + + /* We expect the chef to receive an order for a hotdog */ + expect_string(__wrap_chef_cook, order, "hotdog"); + /* And we tell the test chef that ke knows how to cook a hotdog + * and has the ingredients + */ + will_return(__wrap_chef_cook, true); + will_return(__wrap_chef_cook, true); + /* The result will be a burger and the cooking process will succeed. + * We expect the waiter to handle the bad dish and return an error + * code + */ + will_return(__wrap_chef_cook, "burger"); + will_return(__wrap_chef_cook, 0); + + /* Test the waiter */ + rv = waiter_process("hotdog", &dish); + + /* According to the documentation the waiter should return -2 now */ + assert_int_equal(rv, -2); + /* And do not give the bad dish to the customer */ + assert_null(dish); +} + +int main(void) +{ + const UnitTest tests[] = { + unit_test(test_order_hotdog), + unit_test(test_bad_dish), + }; + + return run_tests(tests); +} diff --git a/example/chef_wrap/waiter_test_wrap.h b/example/chef_wrap/waiter_test_wrap.h new file mode 100644 index 0000000..9178ca2 --- /dev/null +++ b/example/chef_wrap/waiter_test_wrap.h @@ -0,0 +1,2 @@ + +int __wrap_chef_cook(const char *order, char **dish_out); -- 2.7.4