summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Ates <joseph.ates@msasafety.com>2015-11-12 13:08:15 -0500
committerAndreas Schneider <asn@cryptomilk.org>2015-12-18 12:21:34 +0100
commit443b02803dd35f34f6823e31791bc85fc07b9629 (patch)
treef1a5e7ecd3a19255e5881668cff88a7d23b1ed5b
parentd1f796100c35ed89ce2ecd90cae564a0e9ac2315 (diff)
downloadcmocka-443b02803dd35f34f6823e31791bc85fc07b9629.tar.gz
cmocka-443b02803dd35f34f6823e31791bc85fc07b9629.tar.bz2
cmocka-443b02803dd35f34f6823e31791bc85fc07b9629.zip
cmocka: Add support to verify call ordering
-rw-r--r--include/cmocka.h141
-rw-r--r--src/cmocka.c191
-rw-r--r--src/cmocka.def2
3 files changed, 322 insertions, 12 deletions
diff --git a/include/cmocka.h b/include/cmocka.h
index 72e4e5e..6242ff2 100644
--- a/include/cmocka.h
+++ b/include/cmocka.h
@@ -1336,6 +1336,138 @@ void assert_not_in_set(LargestIntegralType value, LargestIntegralType values[],
/** @} */
/**
+ * @defgroup cmocka_call_order Call Ordering
+ * @ingroup cmocka
+ *
+ * It is often beneficial to make sure that functions are called in an
+ * order. This is independent of mock returns and parameter checking as both
+ * of the aforementioned do not check the order in which they are called from
+ * different functions.
+ *
+ * <ul>
+ * <li><strong>expect_function_call(function)</strong> - The
+ * expect_function_call() macro pushes an expectation onto the stack of
+ * expected calls.</li>
+ *
+ * <li><strong>function_called()</strong> - pops a value from the stack of
+ * expected calls. function_called() is invoked within the mock object
+ * that uses it.
+ * </ul>
+ *
+ * expect_function_call() and function_called() are intended to be used in
+ * pairs. Cmocka will fail a test if there are more or less expected calls
+ * created (e.g. expect_function_call()) than consumed with function_called().
+ * There are provisions such as ignore_function_calls() which allow this
+ * restriction to be circumvented in tests where mock calls for the code under
+ * test are not the focus of the test.
+ *
+ * The following example illustrates how a unit test instructs cmocka
+ * to expect a function_called() from a particular mock,
+ * <strong>chef_sing()</strong>:
+ *
+ * @code
+ * void chef_sing(void);
+ *
+ * void code_under_test()
+ * {
+ * chef_sing();
+ * }
+ *
+ * void some_test(void **state)
+ * {
+ * expect_function_call(chef_sing);
+ * code_under_test();
+ * }
+ * @endcode
+ *
+ * The implementation of the mock then must check whether it was meant to
+ * be called by invoking <strong>function_called()</strong>:
+ *
+ * @code
+ * void chef_sing()
+ * {
+ * function_called();
+ * }
+ * @endcode
+ *
+ * @{
+ */
+
+#ifdef DOXYGEN
+/**
+ * @brief Check that current mocked function is being called in the expected
+ * order
+ *
+ * @see expect_function_call()
+ */
+void function_called(void);
+#else
+#define function_called() _function_called(__func__, __FILE__, __LINE__)
+#endif
+
+#ifdef DOXYGEN
+/**
+ * @brief Store expected call(s) to a mock to be checked by function_called()
+ * later.
+ *
+ * @param[in] #function The function which should should be called
+ *
+ * @param[in] times number of times this mock must be called
+ *
+ * @see function_called()
+ */
+void expect_function_calls(#function, const int times);
+#else
+#define expect_function_calls(function, times) \
+ _expect_function_call(#function, __FILE__, __LINE__, times)
+#endif
+
+#ifdef DOXYGEN
+/**
+ * @brief Store expected single call to a mock to be checked by
+ * function_called() later.
+ *
+ * @param[in] #function The function which should should be called
+ *
+ * @see function_called()
+ */
+void expect_function_call(#function);
+#else
+#define expect_function_call(function) \
+ _expect_function_call(#function, __FILE__, __LINE__, 1)
+#endif
+
+#ifdef DOXYGEN
+/**
+ * @brief Expects function_called() from given mock at least once
+ *
+ * @param[in] #function The function which should should be called
+ *
+ * @see function_called()
+ */
+void expect_function_call_any(#function);
+#else
+#define expect_function_call_any(function) \
+ _expect_function_call(#function, __FILE__, __LINE__, -1)
+#endif
+
+#ifdef DOXYGEN
+/**
+ * @brief Ignores function_called() invocations from given mock function.
+ *
+ * @param[in] #function The function which should should be called
+ *
+ * @see function_called()
+ */
+void ignore_function_calls(#function);
+#else
+#define ignore_function_calls(function) \
+ _expect_function_call(#function, __FILE__, __LINE__, -2)
+#endif
+
+/** @} */
+
+/**
* @defgroup cmocka_exec Running Tests
* @ingroup cmocka
*
@@ -1938,6 +2070,15 @@ extern const char * global_last_failed_assert;
LargestIntegralType _mock(const char * const function, const char* const file,
const int line);
+void _expect_function_call(
+ const char * const function_name,
+ const char * const file,
+ const int line,
+ const int count);
+
+void _function_called(const char * const function, const char* const file,
+ const int line);
+
void _expect_check(
const char* const function, const char* const parameter,
const char* const file, const int line,
diff --git a/src/cmocka.c b/src/cmocka.c
index a4dbae8..310073d 100644
--- a/src/cmocka.c
+++ b/src/cmocka.c
@@ -174,6 +174,12 @@ typedef struct SymbolMapValue {
ListNode symbol_values_list_head;
} SymbolMapValue;
+/* Where a particular ordering was located and its symbol name */
+typedef struct FuncOrderingValue {
+ SourceLocation location;
+ const char * function;
+} FuncOrderingValue;
+
/* Used by list_free() to deallocate values referenced by list nodes. */
typedef void (*CleanupListValue)(const void *value, void *cleanup_value_data);
@@ -229,9 +235,16 @@ static void free_symbol_map_value(
const void *value, void *cleanup_value_data);
static void remove_always_return_values(ListNode * const map_head,
const size_t number_of_symbol_names);
+
+static int check_for_leftover_values_list(const ListNode * head,
+ const char * const error_message);
+
static int check_for_leftover_values(
const ListNode * const map_head, const char * const error_message,
const size_t number_of_symbol_names);
+
+static void remove_always_return_values_from_list(ListNode * const map_head);
+
/*
* This must be called at the beginning of a test to initialize some data
* structures.
@@ -274,6 +287,11 @@ static CMOCKA_THREAD ListNode global_function_parameter_map_head;
/* Location of last parameter value checked was declared. */
static CMOCKA_THREAD SourceLocation global_last_parameter_location;
+/* List (acting as FIFO) of call ordering. */
+static CMOCKA_THREAD ListNode global_call_ordering_head;
+/* Location of last call ordering that was declared. */
+static CMOCKA_THREAD SourceLocation global_last_call_ordering_location;
+
/* List of all currently allocated blocks. */
static CMOCKA_THREAD ListNode global_allocated_blocks;
@@ -401,17 +419,19 @@ static void set_source_location(
/* Create function results and expected parameter lists. */
void initialize_testing(const char *test_name) {
- (void)test_name;
+ (void)test_name;
list_initialize(&global_function_result_map_head);
initialize_source_location(&global_last_mock_value_location);
list_initialize(&global_function_parameter_map_head);
initialize_source_location(&global_last_parameter_location);
+ list_initialize(&global_call_ordering_head);
+ initialize_source_location(&global_last_parameter_location);
}
static void fail_if_leftover_values(const char *test_name) {
int error_occurred = 0;
- (void)test_name;
+ (void)test_name;
remove_always_return_values(&global_function_result_map_head, 1);
if (check_for_leftover_values(
&global_function_result_map_head,
@@ -425,6 +445,12 @@ static void fail_if_leftover_values(const char *test_name) {
"%s parameter still has values that haven't been checked.\n", 2)) {
error_occurred = 1;
}
+
+ remove_always_return_values_from_list(&global_call_ordering_head);
+ if (check_for_leftover_values_list(&global_call_ordering_head,
+ "%s function was expected to be called but was not not.\n")) {
+ error_occurred = 1;
+ }
if (error_occurred) {
exit_test(1);
}
@@ -432,13 +458,16 @@ static void fail_if_leftover_values(const char *test_name) {
static void teardown_testing(const char *test_name) {
- (void)test_name;
+ (void)test_name;
list_free(&global_function_result_map_head, free_symbol_map_value,
(void*)0);
initialize_source_location(&global_last_mock_value_location);
list_free(&global_function_parameter_map_head, free_symbol_map_value,
(void*)1);
initialize_source_location(&global_last_parameter_location);
+ list_free(&global_call_ordering_head, free_value,
+ (void*)0);
+ initialize_source_location(&global_last_call_ordering_location);
}
/* Initialize a list node. */
@@ -557,7 +586,7 @@ static int list_first(ListNode * const head, ListNode **output) {
/* Deallocate a value referenced by a list. */
static void free_value(const void *value, void *cleanup_value_data) {
- (void)cleanup_value_data;
+ (void)cleanup_value_data;
assert_non_null(value);
free((void*)value);
}
@@ -585,7 +614,6 @@ static int symbol_names_match(const void *map_value, const void *symbol) {
(const char*)symbol);
}
-
/*
* Adds a value to the queue of values associated with the given hierarchy of
* symbols. It's assumed value is allocated from the heap.
@@ -675,6 +703,26 @@ static int get_symbol_value(
return 0;
}
+/**
+ * Taverse a list of nodes and remove first symbol value in list that has a
+ * refcount < -1 (i.e. should always be returned and has been returned at
+ * least once).
+ */
+
+static void remove_always_return_values_from_list(ListNode * const map_head)
+{
+ ListNode * current = NULL;
+ ListNode * next = NULL;
+ assert_non_null(map_head);
+
+ for (current = map_head->next, next = current->next;
+ current != map_head;
+ current = next, next = current->next) {
+ if (current->refcount < -1) {
+ list_remove_free(current, free_value, NULL);
+ }
+ }
+}
/*
* Traverse down a tree of symbol values and remove the first symbol value
@@ -714,6 +762,26 @@ static void remove_always_return_values(ListNode * const map_head,
}
}
+static int check_for_leftover_values_list(const ListNode * head,
+ const char * const error_message)
+{
+ ListNode *child_node;
+ int leftover_count = 0;
+ if (!list_empty(head))
+ {
+ for (child_node = head->next; child_node != head;
+ child_node = child_node->next, ++leftover_count) {
+ const FuncOrderingValue *const o =
+ (const FuncOrderingValue*) child_node->value;
+ cm_print_error(error_message, o->function);
+ cm_print_error(SOURCE_LOCATION_FORMAT
+ ": note: remaining item was declared here\n",
+ o->location.file, o->location.line);
+ }
+ }
+ return leftover_count;
+}
+
/*
* Checks if there are any leftover values set up by the test that were never
* retrieved through execution, and fail the test if that is the case.
@@ -790,13 +858,87 @@ LargestIntegralType _mock(const char * const function, const char* const file,
return 0;
}
+/* Ensure that function is being called in proper order */
+void _function_called(const char *const function,
+ const char *const file,
+ const int line)
+{
+ ListNode *first_value_node = NULL;
+ ListNode *value_node = NULL;
+ FuncOrderingValue *expected_call;
+ int rc;
+
+ rc = list_first(&global_call_ordering_head, &value_node);
+ first_value_node = value_node;
+ if (rc) {
+ int cmp;
+
+ expected_call = (FuncOrderingValue *)value_node->value;
+ cmp = strcmp(expected_call->function, function);
+ if (value_node->refcount < -1) {
+ /*
+ * Search through value nodes until either function is found or
+ * encounter a non-zero refcount greater than -2
+ */
+ if (cmp != 0) {
+ value_node = value_node->next;
+ expected_call = (FuncOrderingValue *)value_node->value;
+
+ cmp = strcmp(expected_call->function, function);
+ while (value_node->refcount < -1 &&
+ cmp != 0 &&
+ value_node != first_value_node->prev) {
+ value_node = value_node->next;
+ if (value_node == NULL) {
+ break;
+ }
+ expected_call = (FuncOrderingValue *)value_node->value;
+ if (expected_call == NULL) {
+ continue;
+ }
+ cmp = strcmp(expected_call->function, function);
+ }
+
+ if (value_node == first_value_node->prev) {
+ cm_print_error(SOURCE_LOCATION_FORMAT
+ ": error: No expected mock calls matching "
+ "called() invocation in %s",
+ file, line,
+ function);
+ exit_test(1);
+ }
+ }
+ }
+
+ if (cmp == 0) {
+ if (value_node->refcount > -2 && --value_node->refcount == 0) {
+ list_remove_free(value_node, free_value, NULL);
+ }
+ } else {
+ cm_print_error(SOURCE_LOCATION_FORMAT
+ ": error: Expected call to %s but received called() "
+ "in %s\n",
+ file, line,
+ expected_call->function,
+ function);
+ exit_test(1);
+ }
+ } else {
+ cm_print_error(SOURCE_LOCATION_FORMAT
+ ": error: No mock calls expected but called() was "
+ "invoked in %s\n",
+ file, line,
+ function);
+ exit_test(1);
+ }
+}
/* Add a return value for the specified mock function name. */
void _will_return(const char * const function_name, const char * const file,
const int line, const LargestIntegralType value,
const int count) {
SymbolValue * const return_value =
- (SymbolValue*)malloc(sizeof(*return_value));
+ (SymbolValue*)malloc(sizeof(*return_value));
assert_true(count > 0 || count == -1);
return_value->value = value;
set_source_location(&return_value->location, file, line);
@@ -828,6 +970,31 @@ void _expect_check(
count);
}
+/*
+ * Add an call expectations that a particular function is called correctly.
+ * This is used for code under test that makes calls to several functions
+ * in depended upon components (mocks).
+ */
+
+void _expect_function_call(
+ const char * const function_name,
+ const char * const file,
+ const int line,
+ const int count)
+{
+ FuncOrderingValue *ordering;
+
+ assert_non_null(function_name);
+ assert_non_null(file);
+ assert_true(count != 0);
+
+ ordering = (FuncOrderingValue *)malloc(sizeof(*ordering));
+
+ set_source_location(&ordering->location, file, line);
+ ordering->function = function_name;
+
+ list_add_value(&global_call_ordering_head, ordering, count);
+}
/* Returns 1 if the specified values are equal. If the values are not equal
* an error is displayed and 0 is returned. */
@@ -1234,7 +1401,7 @@ static void expect_memory_setup(
const void * const memory, const size_t size,
const CheckParameterValue check_function, const int count) {
CheckMemoryData * const check_data =
- (CheckMemoryData*)malloc(sizeof(*check_data) + size);
+ (CheckMemoryData*)malloc(sizeof(*check_data) + size);
void * const mem = (void*)(check_data + 1);
declare_initialize_value_pointer_pointer(check_data_pointer, check_data);
assert_non_null(memory);
@@ -1266,7 +1433,7 @@ static int check_not_memory(const LargestIntegralType value,
assert_non_null(check);
return memory_not_equal_display_error(
cast_largest_integral_type_to_pointer(const char*, value),
- (const char*)check->memory,
+ (const char*)check->memory,
check->size);
}
@@ -1284,8 +1451,8 @@ void _expect_not_memory(
/* CheckParameterValue callback that always returns 1. */
static int check_any(const LargestIntegralType value,
const LargestIntegralType check_value_data) {
- (void)value;
- (void)check_value_data;
+ (void)value;
+ (void)check_value_data;
return 1;
}
@@ -1734,7 +1901,7 @@ static int display_allocated_blocks(const ListNode * const check_point) {
for (node = check_point->next; node != head; node = node->next) {
const MallocBlockInfo * const block_info =
- (const MallocBlockInfo*)node->value;
+ (const MallocBlockInfo*)node->value;
assert_non_null(block_info);
if (!allocated_blocks) {
@@ -2762,7 +2929,7 @@ int _run_tests(const UnitTest * const tests, const size_t number_of_tests) {
* when a test setup occurs and popped on tear down.
*/
TestState* test_states =
- (TestState*)malloc(number_of_tests * sizeof(*test_states));
+ (TestState*)malloc(number_of_tests * sizeof(*test_states));
/* The number of test states which should be 0 at the end */
long number_of_test_states = 0;
/* Names of the tests that failed. */
diff --git a/src/cmocka.def b/src/cmocka.def
index 607ad75..43228c4 100644
--- a/src/cmocka.def
+++ b/src/cmocka.def
@@ -16,6 +16,7 @@ EXPORTS
_cmocka_run_group_tests
_expect_any
_expect_check
+ _expect_function_call
_expect_in_range
_expect_in_set
_expect_memory
@@ -27,6 +28,7 @@ EXPORTS
_expect_string
_expect_value
_fail
+ _function_called
_mock
_run_test
_run_tests