example: wrap trick
authorJakub Hrozek <jhrozek@redhat.com>
Mon, 3 Jun 2013 14:42:20 +0000 (16:42 +0200)
committerJakub Hrozek <jhrozek@redhat.com>
Mon, 3 Jun 2013 16:15:51 +0000 (18:15 +0200)
Adds a new example that illustrates the gcc wrap trick.

example/CMakeLists.txt
example/chef_wrap/CMakeLists.txt [new file with mode: 0644]
example/chef_wrap/chef.c [new file with mode: 0644]
example/chef_wrap/chef.h [new file with mode: 0644]
example/chef_wrap/waiter_test_wrap.c [new file with mode: 0644]
example/chef_wrap/waiter_test_wrap.h [new file with mode: 0644]

index a060de1..d03c40a 100644 (file)
@@ -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 (file)
index 0000000..b6bd6b5
--- /dev/null
@@ -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 (file)
index 0000000..1429cde
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 (c) Andreas Schneider <asn@cynapses.org>
+ *                    Jakub Hrozek <jakub.hrozek@gmail.com>
+ *
+ * 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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+
+#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 (file)
index 0000000..c1a01c7
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2013 (c) Andreas Schneider <asn@cynapses.org>
+ *                    Jakub Hrozek <jakub.hrozek@gmail.com>
+ *
+ * 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 (file)
index 0000000..01afa15
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2013 (c) Andreas Schneider <asn@cynapses.org>
+ *                    Jakub Hrozek <jakub.hrozek@gmail.com>
+ *
+ * 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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+
+#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 (file)
index 0000000..9178ca2
--- /dev/null
@@ -0,0 +1,2 @@
+
+int __wrap_chef_cook(const char *order, char **dish_out);