/* * Copyright 2008 Google Inc. * * 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 #ifndef _WIN32 #include #endif // !_WIN32 #include #include #include #include #include #ifdef _WIN32 #include #endif // _WIN32 #include #ifdef _WIN32 #define vsnprintf _vsnprintf #endif // _WIN32 // Size of guard bytes around dynamically allocated blocks. #define MALLOC_GUARD_SIZE 16 // Pattern used to initialize guard blocks. #define MALLOC_GUARD_PATTERN 0xEF // Pattern used to initialize memory allocated with test_malloc(). #define MALLOC_ALLOC_PATTERN 0xBA #define MALLOC_FREE_PATTERN 0xCD // Alignment of allocated blocks. NOTE: This must be base2. #define MALLOC_ALIGNMENT sizeof(size_t) // Printf formatting for source code locations. #define SOURCE_LOCATION_FORMAT "%s:%d" // Calculates the number of elements in an array. #define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) // Doubly linked list node. typedef struct ListNode { const void *value; int refcount; struct ListNode *next; struct ListNode *prev; } ListNode; // Debug information for malloc(). typedef struct MallocBlockInfo { void* block; // Address of the block returned by malloc(). size_t allocated_size; // Total size of the allocated block. size_t size; // Request block size. SourceLocation location; // Where the block was allocated. ListNode node; // Node within list of all allocated blocks. } MallocBlockInfo; // State of each test. typedef struct TestState { const ListNode *check_point; // Check point of the test if there's a // setup function. void *state; // State associated with the test. } TestState; // Determines whether two values are the same. typedef int (*EqualityFunction)(const void *left, const void *right); // Value of a symbol and the place it was declared. typedef struct SymbolValue { SourceLocation location; const void* value; } SymbolValue; /* Contains a list of values for a symbol. * NOTE: Each structure referenced by symbol_values_list_head must have a * SourceLocation as its' first member. */ typedef struct SymbolMapValue { const char *symbol_name; ListNode symbol_values_list_head; } SymbolMapValue; // Used by list_free() to deallocate values referenced by list nodes. typedef void (*CleanupListValue)(const void *value, void *cleanup_value_data); // Structure used to check the range of integer types. typedef struct CheckIntegerRange { CheckParameterEvent event; int minimum; int maximum; } CheckIntegerRange; // Structure used to check whether an integer value is in a set. typedef struct CheckIntegerSet { CheckParameterEvent event; const void **set; size_t size_of_set; } CheckIntegerSet; /* Used to check whether a parameter matches the area of memory referenced by * this structure. */ typedef struct CheckMemoryData { CheckParameterEvent event; const void *memory; size_t size; } CheckMemoryData; static ListNode* list_initialize(ListNode * const node); static ListNode* list_add(ListNode * const head, ListNode *new_node); static ListNode* list_add_value(ListNode * const head, const void *value, const int count); static ListNode* list_remove( ListNode * const node, const CleanupListValue cleanup_value, void * const cleanup_value_data); static void list_remove_free( ListNode * const node, const CleanupListValue cleanup_value, void * const cleanup_value_data); static int list_empty(const ListNode * const head); static int list_find( ListNode * const head, const void *value, const EqualityFunction equal_func, ListNode **output); static int list_first(ListNode * const head, ListNode **output); static ListNode* list_free( ListNode * const head, const CleanupListValue cleanup_value, void * const cleanup_value_data); static void add_symbol_value( ListNode * const symbol_map_head, const char * const symbol_names[], const size_t number_of_symbol_names, const void* value, const int count); static int get_symbol_value( ListNode * const symbol_map_head, const char * const symbol_names[], const size_t number_of_symbol_names, void **output); static void free_value(const void *value, void *cleanup_value_data); 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( const ListNode * const map_head, const char * const error_message, const size_t number_of_symbol_names); // This must be called at the beginning of a test to initialize some data // structures. static void initialize_testing(const char *test_name); // This must be called at the end of a test to free() allocated structures. static void teardown_testing(const char *test_name); // Keeps track of the calling context returned by setenv() so that the fail() // method can jump out of a test. static jmp_buf global_run_test_env; static int global_running_test = 0; // Keeps track of the calling context returned by setenv() so that // mock_assert() can optionally jump back to expect_assert_failure(). jmp_buf global_expect_assert_env; int global_expecting_assert = 0; // Keeps a map of the values that functions will have to return to provide // mocked interfaces. static ListNode global_function_result_map_head; // Location of the last mock value returned was declared. static SourceLocation global_last_mock_value_location; /* Keeps a map of the values that functions expect as parameters to their * mocked interfaces. */ static ListNode global_function_parameter_map_head; // Location of last parameter value checked was declared. static SourceLocation global_last_parameter_location; // List of all currently allocated blocks. static ListNode global_allocated_blocks; #ifndef _WIN32 // Signals caught by exception_handler(). static const int exception_signals[] = { SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGSYS, }; // Default signal functions that should be restored after a test is complete. typedef void (*SignalFunction)(int signal); static SignalFunction default_signal_functions[ ARRAY_LENGTH(exception_signals)]; #else // _WIN32 // The default exception filter. static LPTOP_LEVEL_EXCEPTION_FILTER previous_exception_filter; // Fatal exceptions. typedef struct ExceptionCodeInfo { DWORD code; const char* description; } ExceptionCodeInfo; #define EXCEPTION_CODE_INFO(exception_code) {exception_code, #exception_code} static const ExceptionCodeInfo exception_codes[] = { EXCEPTION_CODE_INFO(EXCEPTION_ACCESS_VIOLATION), EXCEPTION_CODE_INFO(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), EXCEPTION_CODE_INFO(EXCEPTION_DATATYPE_MISALIGNMENT), EXCEPTION_CODE_INFO(EXCEPTION_FLT_DENORMAL_OPERAND), EXCEPTION_CODE_INFO(EXCEPTION_FLT_DIVIDE_BY_ZERO), EXCEPTION_CODE_INFO(EXCEPTION_FLT_INEXACT_RESULT), EXCEPTION_CODE_INFO(EXCEPTION_FLT_INVALID_OPERATION), EXCEPTION_CODE_INFO(EXCEPTION_FLT_OVERFLOW), EXCEPTION_CODE_INFO(EXCEPTION_FLT_STACK_CHECK), EXCEPTION_CODE_INFO(EXCEPTION_FLT_UNDERFLOW), EXCEPTION_CODE_INFO(EXCEPTION_GUARD_PAGE), EXCEPTION_CODE_INFO(EXCEPTION_ILLEGAL_INSTRUCTION), EXCEPTION_CODE_INFO(EXCEPTION_INT_DIVIDE_BY_ZERO), EXCEPTION_CODE_INFO(EXCEPTION_INT_OVERFLOW), EXCEPTION_CODE_INFO(EXCEPTION_INVALID_DISPOSITION), EXCEPTION_CODE_INFO(EXCEPTION_INVALID_HANDLE), EXCEPTION_CODE_INFO(EXCEPTION_IN_PAGE_ERROR), EXCEPTION_CODE_INFO(EXCEPTION_NONCONTINUABLE_EXCEPTION), EXCEPTION_CODE_INFO(EXCEPTION_PRIV_INSTRUCTION), EXCEPTION_CODE_INFO(EXCEPTION_STACK_OVERFLOW), }; #endif // !_WIN32 // Exit the currently executing test. static void exit_test(const int quit_application) { if (global_running_test) { longjmp(global_run_test_env, 1); } else if (quit_application) { exit(-1); } } // Initialize a SourceLocation structure. static void initialize_source_location(SourceLocation * const location) { assert_true(location); location->file = NULL; location->line = 0; } // Determine whether a source location is currently set. static int source_location_is_set(const SourceLocation * const location) { assert_true(location); return location->file && location->line; } // Set a source location. static void set_source_location( SourceLocation * const location, const char * const file, const int line) { assert_true(location); location->file = file; location->line = line; } // Create function results and expected parameter lists. void initialize_testing(const char *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); } void fail_if_leftover_values(const char *test_name) { int error_occurred = 0; remove_always_return_values(&global_function_result_map_head, 1); if (check_for_leftover_values( &global_function_result_map_head, "%s() has remaining non-returned values.\n", 1)) { error_occurred = 1; } remove_always_return_values(&global_function_parameter_map_head, 2); if (check_for_leftover_values( &global_function_parameter_map_head, "%s parameter still has values that haven't been checked.\n", 2)) { error_occurred = 1; } if (error_occurred) { exit_test(1); } } void teardown_testing(const char *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); } // Initialize a list node. static ListNode* list_initialize(ListNode * const node) { node->value = NULL; node->next = node; node->prev = node; node->refcount = 1; return node; } /* Adds a value at the tail of a given list. * The node referencing the value is allocated from the heap. */ static ListNode* list_add_value(ListNode * const head, const void *value, const int refcount) { ListNode * const new_node = (ListNode*)malloc(sizeof(ListNode)); assert_true(head); assert_true(value); new_node->value = value; new_node->refcount = refcount; return list_add(head, new_node); } // Add new_node to the end of the list. static ListNode* list_add(ListNode * const head, ListNode *new_node) { assert_true(head); assert_true(new_node); new_node->next = head; new_node->prev = head->prev; head->prev->next = new_node; head->prev = new_node; return new_node; } // Remove a node from a list. static ListNode* list_remove( ListNode * const node, const CleanupListValue cleanup_value, void * const cleanup_value_data) { assert_true(node); node->prev->next = node->next; node->next->prev = node->prev; if (cleanup_value) { cleanup_value(node->value, cleanup_value_data); } return node; } /* Remove a list node from a list and free the node. */ static void list_remove_free( ListNode * const node, const CleanupListValue cleanup_value, void * const cleanup_value_data) { assert_true(node); free(list_remove(node, cleanup_value, cleanup_value_data)); } /* Frees memory kept by a linked list * The cleanup_value function is called for every "value" field of nodes in the * list, except for the head. In addition to each list value, * cleanup_value_data is passed to each call to cleanup_value. The head * of the list is not deallocated. */ static ListNode* list_free( ListNode * const head, const CleanupListValue cleanup_value, void * const cleanup_value_data) { assert_true(head); while (!list_empty(head)) { list_remove_free(head->next, cleanup_value, cleanup_value_data); } return head; } // Determine whether a list is empty. static int list_empty(const ListNode * const head) { assert_true(head); return head->next == head; } /* Find a value in the list using the equal_func to compare each node with the * value. */ static int list_find(ListNode * const head, const void *value, const EqualityFunction equal_func, ListNode **output) { ListNode *current; assert_true(head); for (current = head->next; current != head; current = current->next) { if (equal_func(current->value, value)) { *output = current; return 1; } } return 0; } // Returns the first node of a list static int list_first(ListNode * const head, ListNode **output) { ListNode *target_node; assert_true(head); if (list_empty(head)) { return 0; } target_node = head->next; *output = target_node; return 1; } // Deallocate a value referenced by a list. static void free_value(const void *value, void *cleanup_value_data) { assert_true(value); free((void*)value); } // Releases memory associated to a symbol_map_value. static void free_symbol_map_value(const void *value, void *cleanup_value_data) { SymbolMapValue * const map_value = (SymbolMapValue*)value; const unsigned int children = (unsigned int)cleanup_value_data; assert_true(value); list_free(&map_value->symbol_values_list_head, children ? free_symbol_map_value : free_value, (void*)(children - 1)); free(map_value); } /* Determine whether a symbol name referenced by a symbol_map_value * matches the specified function name. */ static int symbol_names_match(const void *map_value, const void *symbol) { return !strcmp(((SymbolMapValue*)map_value)->symbol_name, (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. */ static void add_symbol_value(ListNode * const symbol_map_head, const char * const symbol_names[], const size_t number_of_symbol_names, const void* value, const int refcount) { const char* symbol_name; ListNode *target_node; SymbolMapValue *target_map_value; assert_true(symbol_map_head); assert_true(symbol_names); assert_true(number_of_symbol_names); symbol_name = symbol_names[0]; if (!list_find(symbol_map_head, symbol_name, symbol_names_match, &target_node)) { SymbolMapValue * const new_symbol_map_value = malloc(sizeof(*new_symbol_map_value)); new_symbol_map_value->symbol_name = symbol_name; list_initialize(&new_symbol_map_value->symbol_values_list_head); target_node = list_add_value(symbol_map_head, new_symbol_map_value, 1); } target_map_value = (SymbolMapValue*)target_node->value; if (number_of_symbol_names == 1) { list_add_value(&target_map_value->symbol_values_list_head, value, refcount); } else { add_symbol_value(&target_map_value->symbol_values_list_head, &symbol_names[1], number_of_symbol_names - 1, value, refcount); } } /* Gets the next value associated with the given hierarchy of symbols. * The value is returned as an output parameter with the function returning the * node's old refcount value if a value is found, 0 otherwise. * This means that a return value of 1 indicates the node was just removed from * the list. */ static int get_symbol_value( ListNode * const head, const char * const symbol_names[], const size_t number_of_symbol_names, void **output) { const char* symbol_name; ListNode *target_node; assert_true(head); assert_true(symbol_names); assert_true(number_of_symbol_names); assert_true(output); symbol_name = symbol_names[0]; if (list_find(head, symbol_name, symbol_names_match, &target_node)) { SymbolMapValue *map_value; ListNode *child_list; int return_value = 0; assert_true(target_node); assert_true(target_node->value); map_value = (SymbolMapValue*)target_node->value; child_list = &map_value->symbol_values_list_head; if (number_of_symbol_names == 1) { ListNode *value_node = NULL; return_value = list_first(child_list, &value_node); assert_true(return_value); *output = (void*) value_node->value; return_value = value_node->refcount; if (--value_node->refcount == 0) { list_remove_free(value_node, NULL, NULL); } } else { return_value = get_symbol_value( child_list, &symbol_names[1], number_of_symbol_names - 1, output); } if (list_empty(child_list)) { list_remove_free(target_node, free_symbol_map_value, (void*)0); } return return_value; } else { print_error("No entries for symbol %s.\n", symbol_name); } return 0; } /* Traverse down a tree of symbol values and remove the first symbol value * in each branch that has a refcount < -1 (i.e should always be returned * and has been returned at least once). */ static void remove_always_return_values(ListNode * const map_head, const size_t number_of_symbol_names) { ListNode *current; assert_true(map_head); assert_true(number_of_symbol_names); current = map_head->next; while (current != map_head) { SymbolMapValue * const value = (SymbolMapValue*)current->value; ListNode * const next = current->next; ListNode *child_list; assert_true(value); child_list = &value->symbol_values_list_head; if (!list_empty(child_list)) { if (number_of_symbol_names == 1) { ListNode * const child_node = child_list->next; // If this item has been returned more than once, free it. if (child_node->refcount < -1) { list_remove_free(child_node, free_value, NULL); } } else { remove_always_return_values(child_list, number_of_symbol_names - 1); } } if (list_empty(child_list)) { list_remove_free(current, free_value, NULL); } current = next; } } /* 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. */ static int check_for_leftover_values( const ListNode * const map_head, const char * const error_message, const size_t number_of_symbol_names) { const ListNode *current; int symbols_with_leftover_values = 0; assert_true(map_head); assert_true(number_of_symbol_names); for (current = map_head->next; current != map_head; current = current->next) { const SymbolMapValue * const value = (SymbolMapValue*)current->value; const ListNode *child_list; assert_true(value); child_list = &value->symbol_values_list_head; if (!list_empty(child_list)) { if (number_of_symbol_names == 1) { const ListNode *child_node; print_error(error_message, value->symbol_name); print_error(" Remaining item(s) declared at...\n"); for (child_node = child_list->next; child_node != child_list; child_node = child_node->next) { const SourceLocation * const location = child_node->value; print_error(" " SOURCE_LOCATION_FORMAT "\n", location->file, location->line); } } else { print_error("%s.", value->symbol_name); check_for_leftover_values(child_list, error_message, number_of_symbol_names - 1); } symbols_with_leftover_values ++; } } return symbols_with_leftover_values; } // Get the next return value for the specified mock function. void* _mock(const char * const function, const char* const file, const int line) { void *result; const int rc = get_symbol_value(&global_function_result_map_head, &function, 1, &result); if (rc) { SymbolValue * const symbol = result; void * const value = (void*)symbol->value; global_last_mock_value_location = symbol->location; if (rc == 1) { free(symbol); } return value; } else { print_error("ERROR: " SOURCE_LOCATION_FORMAT " - Could not get value " "to mock function %s\n", file, line, function); if (source_location_is_set(&global_last_mock_value_location)) { print_error("Previously returned mock value was declared at " SOURCE_LOCATION_FORMAT "\n", global_last_mock_value_location.file, global_last_mock_value_location.line); } else { print_error("There were no previously returned mock values for " "this test.\n"); } exit_test(1); } return NULL; } // 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 void* const value, const int count) { SymbolValue * const return_value = malloc(sizeof(*return_value)); assert_true(count > 0); return_value->value = value; set_source_location(&return_value->location, file, line); add_symbol_value(&global_function_result_map_head, &function_name, 1, return_value, count); } /* Add a custom parameter checking function. If the event parameter is NULL * the event structure is allocated internally by this function. If event * parameter is provided it must be allocated on the heap and doesn't need to * be deallocated by the caller. */ void _expect_check( const char* const function, const char* const parameter, const char* const file, const int line, const CheckParameterValue check_function, void * const check_data, CheckParameterEvent * const event, const int count) { CheckParameterEvent * const check = event ? event : malloc(sizeof(*check)); const char* symbols[] = {function, parameter}; check->parameter_name = parameter; check->check_value = check_function; check->check_value_data = check_data; set_source_location(&check->location, file, line); add_symbol_value(&global_function_parameter_map_head, symbols, 2, check, count); } /* Returns 1 if the specified values are equal. If the values are not equal * an error is displayed and 0 is returned. */ static int values_equal_display_error(const void* const left, const void* const right) { const int equal = left == right; if (!equal) { print_error("0x%x != 0x%x\n", left, right); } return equal; } /* Returns 1 if the specified values are not equal. If the values are equal * an error is displayed and 0 is returned. */ static int values_not_equal_display_error(const void* const left, const void* const right) { const int not_equal = left != right; if (!not_equal) { print_error("0x%x == 0x%x\n", left, right); } return not_equal; } /* Determine whether value is contained within check_integer_set. * If invert is 0 and the value is in the set 1 is returned, otherwise 0 is * returned and an error is displayed. If invert is 1 and the value is not * in the set 1 is returned, otherwise 0 is returned and an error is * displayed. */ static int value_in_set_display_error( const void *value, const CheckIntegerSet * const check_integer_set, const int invert) { int succeeded = invert; assert_true(check_integer_set); { const void ** const set = check_integer_set->set; const size_t size_of_set = check_integer_set->size_of_set; size_t i; for (i = 0; i < size_of_set; i++) { if (set[i] == value) { if (invert) { succeeded = 0; } break; } } if (succeeded) { return 1; } print_error("%d is %sin the set (", value, invert ? "" : "not "); for (i = 0; i < size_of_set; i++) { print_error("%d, ", set[i]); } print_error(")\n"); } return 0; } /* Determine whether a value is within the specified range. If the value is * within the specified range 1 is returned. If the value isn't within the * specified range an error is displayed and 0 is returned. */ static int integer_in_range_display_error( const int value, const int range_min, const int range_max) { if (value >= range_min && value <= range_max) { return 1; } print_error("%d is not within the range %d-%d\n", value, range_min, range_max); return 0; } /* Determine whether a value is within the specified range. If the value * is not within the range 1 is returned. If the value is within the * specified range an error is displayed and zero is returned. */ static int integer_not_in_range_display_error( const int value, const int range_min, const int range_max) { if (value < range_min || value > range_max) { return 1; } print_error("%d is within the range %d-%d\n", value, range_min, range_max); return 0; } /* Determine whether the specified strings are equal. If the strings are equal * 1 is returned. If they're not equal an error is displayed and 0 is * returned. */ static int string_equal_display_error( const char * const left, const char * const right) { if (strcmp(left, right) == 0) { return 1; } print_error("\"%s\" != \"%s\"\n", left, right); return 0; } /* Determine whether the specified strings are equal. If the strings are not * equal 1 is returned. If they're not equal an error is displayed and 0 is * returned */ static int string_not_equal_display_error( const char * const left, const char * const right) { if (strcmp(left, right) != 0) { return 1; } print_error("\"%s\" == \"%s\"\n", left, right); return 0; } /* Determine whether the specified areas of memory are equal. If they're equal * 1 is returned otherwise an error is displayed and 0 is returned. */ static int memory_equal_display_error(const char* a, const char* b, const size_t size) { int differences = 0; size_t i; for (i = 0; i < size; i++) { const char l = a[i]; const char r = b[i]; if (l != r) { print_error("difference at offset %d 0x%02x 0x%02x\n", i, l, r); differences ++; } } if (differences) { print_error("%d bytes of 0x%08x and 0x%08x differ\n", differences, a, b); return 0; } return 1; } /* Determine whether the specified areas of memory are not equal. If they're * not equal 1 is returned otherwise an error is displayed and 0 is * returned. */ static int memory_not_equal_display_error(const char* a, const char* b, const size_t size) { int same = 0; size_t i; for (i = 0; i < size; i++) { const char l = a[i]; const char r = b[i]; if (l == r) { print_error("equal at offset %d 0x%02x 0x%02x\n", i, l, r); same ++; } } if (same) { print_error("%d bytes of 0x%08x and 0x%08x the same\n", same, a, b); return 0; } return 1; } // CheckParameterValue callback to check whether a value is within a set. static int check_in_set(const void *value, void *check_value_data) { return value_in_set_display_error(value, (CheckIntegerSet*)check_value_data, 0); } // CheckParameterValue callback to check whether a value isn't within a set. static int check_not_in_set(const void *value, void *check_value_data) { return value_in_set_display_error(value, (CheckIntegerSet*)check_value_data, 1); } /* Create the callback data for check_in_set() or check_not_in_set() and * register a check event. */ static void expect_set( const char* const function, const char* const parameter, const char* const file, const int line, const void *values[], const size_t number_of_values, const CheckParameterValue check_function, const int count) { CheckIntegerSet * const check_integer_set = malloc(sizeof(*check_integer_set) + (sizeof(values[0]) * number_of_values)); void ** const set = (void**)(check_integer_set + 1); assert_true(values); assert_true(number_of_values); memcpy(set, values, number_of_values * sizeof(values[0])); check_integer_set->set = (const void**)set; _expect_check(function, parameter, file, line, check_function, check_integer_set, &check_integer_set->event, count); } // Add an event to check whether a value is in a set. void _expect_in_set( const char* const function, const char* const parameter, const char* const file, const int line, const void *values[], const size_t number_of_values, const int count) { expect_set(function, parameter, file, line, values, number_of_values, check_in_set, count); } // Add an event to check whether a value isn't in a set. void _expect_not_in_set( const char* const function, const char* const parameter, const char* const file, const int line, const void *values[], const size_t number_of_values, const int count) { expect_set(function, parameter, file, line, values, number_of_values, check_not_in_set, count); } // CheckParameterValue callback to check whether a value is within a range. static int check_in_range(const void *value, void *check_value_data) { CheckIntegerRange * const check_integer_range = check_value_data; assert_true(check_value_data); return integer_in_range_display_error( (int)value, check_integer_range->minimum, check_integer_range->maximum); } // CheckParameterValue callback to check whether a value is not within a range. static int check_not_in_range(const void *value, void *check_value_data) { CheckIntegerRange * const check_integer_range = check_value_data; assert_true(check_value_data); return integer_not_in_range_display_error( (int)value, check_integer_range->minimum, check_integer_range->maximum); } /* Create the callback data for check_in_range() or check_not_in_range() and * register a check event. */ static void expect_range( const char* const function, const char* const parameter, const char* const file, const int line, const int minimum, const int maximum, const CheckParameterValue check_function, const int count) { CheckIntegerRange * const check_integer_range = malloc(sizeof(*check_integer_range)); check_integer_range->minimum = minimum; check_integer_range->maximum = maximum; _expect_check(function, parameter, file, line, check_function, check_integer_range, &check_integer_range->event, count); } // Add an event to determine whether a parameter is within a range. void _expect_in_range( const char* const function, const char* const parameter, const char* const file, const int line, const int minimum, const int maximum, const int count) { expect_range(function, parameter, file, line, minimum, maximum, check_in_range, count); } // Add an event to determine whether a parameter is not within a range. void _expect_not_in_range( const char* const function, const char* const parameter, const char* const file, const int line, const int minimum, const int maximum, const int count) { expect_range(function, parameter, file, line, minimum, maximum, check_not_in_range, count); } /* CheckParameterValue callback to check whether a value is equal to an * expected value. */ static int check_value(const void *value, void *check_value_data) { return values_equal_display_error(value, check_value_data); } // Add an event to check a parameter equals an expected value. void _expect_value( const char* const function, const char* const parameter, const char* const file, const int line, const void* const value, const int count) { _expect_check(function, parameter, file, line, check_value, (void*)value, NULL, count); } /* CheckParameterValue callback to check whether a value is not equal to an * expected value. */ static int check_not_value(const void *value, void *check_value_data) { return values_not_equal_display_error(value, check_value_data); } // Add an event to check a parameter is not equal to an expected value. void _expect_not_value( const char* const function, const char* const parameter, const char* const file, const int line, const void* const value, const int count) { _expect_check(function, parameter, file, line, check_not_value, (void*)value, NULL, count); } // CheckParameterValue callback to check whether a parameter equals a string. static int check_string(const void * value, void *check_value_data) { return string_equal_display_error(value, check_value_data); } // Add an event to check whether a parameter is equal to a string. void _expect_string( const char* const function, const char* const parameter, const char* const file, const int line, const char* string, const int count) { _expect_check(function, parameter, file, line, check_string, (void*)string, NULL, count); } /* CheckParameterValue callback to check whether a parameter is not equals to * a string. */ static int check_not_string(const void * value, void *check_value_data) { return string_not_equal_display_error(value, check_value_data); } // Add an event to check whether a parameter is not equal to a string. void _expect_not_string( const char* const function, const char* const parameter, const char* const file, const int line, const char* string, const int count) { _expect_check(function, parameter, file, line, check_not_string, (void*)string, NULL, count); } /* CheckParameterValue callback to check whether a parameter equals an area of * memory. */ static int check_memory(const void* value, void *check_value_data) { CheckMemoryData * const check = (CheckMemoryData*)check_value_data; assert_true(check); return memory_equal_display_error(value, check->memory, check->size); } /* Create the callback data for check_memory() or check_not_memory() and * register a check event. */ static void expect_memory_setup( const char* const function, const char* const parameter, const char* const file, const int line, const void * const memory, const size_t size, const CheckParameterValue check_function, const int count) { CheckMemoryData * const check_data = malloc(sizeof(*check_data) + size); void * const mem = (void*)(check_data + 1); assert_true(memory); assert_true(size); memcpy(mem, memory, size); check_data->memory = mem; check_data->size = size; _expect_check(function, parameter, file, line, check_function, check_data, &check_data->event, count); } // Add an event to check whether a parameter matches an area of memory. void _expect_memory( const char* const function, const char* const parameter, const char* const file, const int line, const void* const memory, const size_t size, const int count) { expect_memory_setup(function, parameter, file, line, memory, size, check_memory, count); } /* CheckParameterValue callback to check whether a parameter is not equal to * an area of memory. */ static int check_not_memory(const void* value, void *check_value_data) { CheckMemoryData * const check = (CheckMemoryData*)check_value_data; assert_true(check); return memory_not_equal_display_error(value, check->memory, check->size); } // Add an event to check whether a parameter doesn't match an area of memory. void _expect_not_memory( const char* const function, const char* const parameter, const char* const file, const int line, const void* const memory, const size_t size, const int count) { expect_memory_setup(function, parameter, file, line, memory, size, check_not_memory, count); } // CheckParameterValue callback that always returns 1. static int check_any(const void *value, void *check_value_data) { return 1; } // Add an event to allow any value for a parameter. void _expect_any( const char* const function, const char* const parameter, const char* const file, const int line, const int count) { _expect_check(function, parameter, file, line, check_any, NULL, NULL, count); } void _check_expected( const char * const function_name, const char * const parameter_name, const char* file, const int line, const void* value) { void *result; const char* symbols[] = {function_name, parameter_name}; const int rc = get_symbol_value(&global_function_parameter_map_head, symbols, 2, &result); if (rc) { CheckParameterEvent * const check = (CheckParameterEvent*)result; int check_succeeded; global_last_parameter_location = check->location; check_succeeded = check->check_value(value, check->check_value_data); if (rc == 1) { free(check); } if (!check_succeeded) { print_error("ERROR: Check of parameter %s, function %s failed\n" "Expected parameter declared at " SOURCE_LOCATION_FORMAT "\n", parameter_name, function_name, global_last_parameter_location.file, global_last_parameter_location.line); _fail(file, line); } } else { print_error("ERROR: " SOURCE_LOCATION_FORMAT " - Could not get value " "to check parameter %s of function %s\n", file, line, parameter_name, function_name); if (source_location_is_set(&global_last_parameter_location)) { print_error("Previously declared parameter value was declared at " SOURCE_LOCATION_FORMAT "\n", global_last_parameter_location.file, global_last_parameter_location.line); } else { print_error("There were no previously declared parameter values " "for this test.\n"); } exit_test(1); } } // Replacement for assert. void mock_assert(const int result, const char* const expression, const char* const file, const int line) { if (!result) { if (global_expecting_assert) { longjmp(global_expect_assert_env, (int)expression); } else { print_error("ASSERT: %s\n", expression); _fail(file, line); } } } void _assert_true(const int result, const char * const expression, const char * const file, const int line) { if (!result) { print_error("%s\n", expression); _fail(file, line); } } void _assert_int_equal(const int a, const int b, const char * const file, const int line) { if (!values_equal_display_error((void*)a, (void*)b)) { _fail(file, line); } } void _assert_int_not_equal(const int a, const int b, const char * const file, const int line) { if (!values_not_equal_display_error((void*)a, (void*)b)) { _fail(file, line); } } void _assert_string_equal(const char * const a, const char * const b, const char * const file, const int line) { if (!string_equal_display_error(a, b)) { _fail(file, line); } } void _assert_string_not_equal(const char * const a, const char * const b, const char *file, const int line) { if (!string_not_equal_display_error(a, b)) { _fail(file, line); } } void _assert_memory_equal(const void * const a, const void * const b, const size_t size, const char* const file, const int line) { if (!memory_equal_display_error((const char*)a, (const char*)b, size)) { _fail(file, line); } } void _assert_memory_not_equal(const void * const a, const void * const b, const size_t size, const char* const file, const int line) { if (!memory_not_equal_display_error((const char*)a, (const char*)b, size)) { _fail(file, line); } } void _assert_in_range(const int value, const int minimum, const int maximum, const char* const file, const int line) { if (!integer_in_range_display_error(value, minimum, maximum)) { _fail(file, line); } } void _assert_not_in_range(const int value, const int minimum, const int maximum, const char* const file, const int line) { if (!integer_not_in_range_display_error(value, minimum, maximum)) { _fail(file, line); } } void _assert_in_set(const void* const value, const void *values[], const size_t number_of_values, const char* const file, const int line) { CheckIntegerSet check_integer_set; check_integer_set.set = values; check_integer_set.size_of_set = number_of_values; if (!value_in_set_display_error(value, &check_integer_set, 0)) { _fail(file, line); } } void _assert_not_in_set(const void* const value, const void *values[], const size_t number_of_values, const char* const file, const int line) { CheckIntegerSet check_integer_set; check_integer_set.set = values; check_integer_set.size_of_set = number_of_values; if (!value_in_set_display_error(value, &check_integer_set, 1)) { _fail(file, line); } } // Get the list of allocated blocks. static ListNode* get_allocated_blocks_list() { // If it initialized, initialize the list of allocated blocks. if (!global_allocated_blocks.value) { list_initialize(&global_allocated_blocks); global_allocated_blocks.value = (void*)1; } return &global_allocated_blocks; } // Use the real malloc in this function. #undef malloc void* _test_malloc(const size_t size, const char* file, const int line) { char* ptr; MallocBlockInfo *block_info; ListNode * const block_list = get_allocated_blocks_list(); const size_t allocate_size = size + (MALLOC_GUARD_SIZE * 2) + sizeof(*block_info) + MALLOC_ALIGNMENT; char* const block = (char*)malloc(allocate_size); assert_true(block); // Calculate the returned address. ptr = (char*)(((size_t)block + MALLOC_GUARD_SIZE + sizeof(*block_info) + MALLOC_ALIGNMENT) & ~(MALLOC_ALIGNMENT - 1)); // Initialize the guard blocks. memset(ptr - MALLOC_GUARD_SIZE, MALLOC_GUARD_PATTERN, MALLOC_GUARD_SIZE); memset(ptr + size, MALLOC_GUARD_PATTERN, MALLOC_GUARD_SIZE); memset(ptr, MALLOC_ALLOC_PATTERN, size); block_info = (MallocBlockInfo*)(ptr - (MALLOC_GUARD_SIZE + sizeof(*block_info))); set_source_location(&block_info->location, file, line); block_info->allocated_size = allocate_size; block_info->size = size; block_info->block = block; block_info->node.value = block_info; list_add(block_list, &block_info->node); return ptr; } #define malloc test_malloc void* _test_calloc(const size_t number_of_elements, const size_t size, const char* file, const int line) { void* const ptr = _test_malloc(number_of_elements * size, file, line); if (ptr) { memset(ptr, 0, number_of_elements * size); } return ptr; } // Use the real free in this function. #undef free void _test_free(void* const ptr, const char* file, const int line) { unsigned int i; char *block = (char*)ptr; MallocBlockInfo *block_info; _assert_true((int)ptr, "ptr", file, line); block_info = (MallocBlockInfo*)(block - (MALLOC_GUARD_SIZE + sizeof(*block_info))); // Check the guard blocks. { char *guards[2] = {block - MALLOC_GUARD_SIZE, block + block_info->size}; for (i = 0; i < ARRAY_LENGTH(guards); i++) { unsigned int j; char * const guard = guards[i]; for (j = 0; j < MALLOC_GUARD_SIZE; j++) { const char diff = guard[j] - MALLOC_GUARD_PATTERN; if (diff) { print_error( "Guard block of 0x%08x size=%d allocated by " SOURCE_LOCATION_FORMAT " at 0x%08x is corrupt\n", (size_t)ptr, block_info->size, block_info->location.file, block_info->location.line, (size_t)&guard[j]); _fail(file, line); } } } } list_remove(&block_info->node, NULL, NULL); block = block_info->block; memset(block, MALLOC_FREE_PATTERN, block_info->allocated_size); free(block); } #define free test_free // Crudely checkpoint the current heap state. static const ListNode* check_point_allocated_blocks() { return get_allocated_blocks_list()->prev; } /* Display the blocks allocated after the specified check point. This * function returns the number of blocks displayed. */ static int display_allocated_blocks(const ListNode * const check_point) { const ListNode * const head = get_allocated_blocks_list(); const ListNode *node; int allocated_blocks = 0; assert_true(check_point); assert_true(check_point->next); for (node = check_point->next; node != head; node = node->next) { const MallocBlockInfo * const block_info = node->value; assert_true(block_info); if (!allocated_blocks) { print_error("Blocks allocated...\n"); } print_error(" 0x%08x : " SOURCE_LOCATION_FORMAT "\n", block_info->block, block_info->location.file, block_info->location.line); allocated_blocks ++; } return allocated_blocks; } // Free all blocks allocated after the specified check point. static void free_allocated_blocks(const ListNode * const check_point) { const ListNode * const head = get_allocated_blocks_list(); const ListNode *node; assert_true(check_point); node = check_point->next; assert_true(node); while (node != head) { MallocBlockInfo * const block_info = (MallocBlockInfo*)node->value; node = node->next; free((char*)block_info + sizeof(*block_info) + MALLOC_GUARD_SIZE); } } // Fail if any any blocks are allocated after the specified check point. static void fail_if_blocks_allocated(const ListNode * const check_point, const char * const test_name) { const int allocated_blocks = display_allocated_blocks(check_point); if (allocated_blocks) { free_allocated_blocks(check_point); print_error("ERROR: %s leaked %d block(s)\n", test_name, allocated_blocks); exit_test(1); } } void _fail(const char * const file, const int line) { print_error("ERROR: " SOURCE_LOCATION_FORMAT " Failure!\n", file, line); exit_test(1); } #ifndef _WIN32 static void exception_handler(int sig) { print_error("%s\n", strsignal(sig)); exit_test(1); } #else // _WIN32 static LONG WINAPI exception_filter(EXCEPTION_POINTERS *exception_pointers) { EXCEPTION_RECORD * const exception_record = exception_pointers->ExceptionRecord; const DWORD code = exception_record->ExceptionCode; unsigned int i; for (i = 0; i < ARRAY_LENGTH(exception_codes); i++) { const ExceptionCodeInfo * const code_info = &exception_codes[i]; if (code == code_info->code) { static int shown_debug_message = 0; fflush(stdout); print_error("%s occurred at 0x%08x.\n", code_info->description, exception_record->ExceptionAddress); if (!shown_debug_message) { print_error( "\n" "To debug in Visual Studio...\n" "1. Select menu item File->Open Project\n" "2. Change 'Files of type' to 'Executable Files'\n" "3. Open this executable.\n" "4. Select menu item Debug->Start\n" "\n" "Alternatively, set the environment variable \n" "UNIT_TESTING_DEBUG to 1 and rebuild this executable, \n" "then click 'Debug' in the popup dialog box.\n" "\n"); shown_debug_message = 1; } exit_test(0); return EXCEPTION_EXECUTE_HANDLER; } } return EXCEPTION_CONTINUE_SEARCH; } #endif // !_WIN32 // Standard output and error print methods. void vprint_message(const char* const format, va_list args) { char buffer[1024]; vsnprintf(buffer, sizeof(buffer), format, args); printf(buffer); #ifdef _WIN32 OutputDebugString(buffer); #endif // _WIN32 } void vprint_error(const char* const format, va_list args) { char buffer[1024]; vsnprintf(buffer, sizeof(buffer), format, args); fprintf(stderr, buffer); #ifdef _WIN32 OutputDebugString(buffer); #endif // _WIN32 } void print_message(const char* const format, ...) { va_list args; va_start(args, format); vprint_message(format, args); va_end(args); } void print_error(const char* const format, ...) { va_list args; va_start(args, format); vprint_error(format, args); va_end(args); } int _run_test( const char * const function_name, const UnitTestFunction Function, void ** const state, const UnitTestFunctionType function_type, const void* const heap_check_point) { const ListNode * const check_point = heap_check_point ? heap_check_point : check_point_allocated_blocks(); void *current_state = NULL; int rc = 1; int handle_exceptions = 1; #ifdef _WIN32 handle_exceptions = !IsDebuggerPresent(); #endif // _WIN32 #if UNIT_TESTING_DEBUG handle_exceptions = 0; #endif // UNIT_TESTING_DEBUG if (handle_exceptions) { #ifndef _WIN32 unsigned int i; for (i = 0; i < ARRAY_LENGTH(exception_signals); i++) { default_signal_functions[i] = signal( exception_signals[i], exception_handler); } #else // _WIN32 previous_exception_filter = SetUnhandledExceptionFilter( exception_filter); #endif // !_WIN32 } if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST) { print_message("%s: Starting test\n", function_name); } initialize_testing(function_name); global_running_test = 1; if (setjmp(global_run_test_env) == 0) { Function(state ? state : ¤t_state); fail_if_leftover_values(function_name); /* If this is a setup function then ignore any allocated blocks * only ensure they're deallocated on tear down. */ if (function_type != UNIT_TEST_FUNCTION_TYPE_SETUP) { fail_if_blocks_allocated(check_point, function_name); } global_running_test = 0; if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST) { print_message("%s: Test completed successfully.\n", function_name); } rc = 0; } else { global_running_test = 0; print_message("%s: Test failed.\n", function_name); } teardown_testing(function_name); if (handle_exceptions) { #ifndef _WIN32 unsigned int i; for (i = 0; i < ARRAY_LENGTH(exception_signals); i++) { signal(exception_signals[i], default_signal_functions[i]); } #else // _WIN32 if (previous_exception_filter) { SetUnhandledExceptionFilter(previous_exception_filter); previous_exception_filter = NULL; } #endif // !_WIN32 } return rc; } int _run_tests(const UnitTest * const tests, const size_t number_of_tests) { // Whether to execute the next test. int run_next_test = 1; // Whether the previous test failed. int previous_test_failed = 0; // Check point of the heap state. const ListNode * const check_point = check_point_allocated_blocks(); // Current test being executed. size_t current_test = 0; // Number of tests executed. size_t tests_executed = 0; // Number of failed tests. size_t total_failed = 0; // Number of setup functions. size_t setups = 0; // Number of teardown functions. size_t teardowns = 0; /* A stack of test states. A state is pushed on the stack * when a test setup occurs and popped on tear down. */ TestState* test_states = malloc(number_of_tests * sizeof(*test_states)); size_t number_of_test_states = 0; // Names of the tests that failed. const char** failed_names = malloc(number_of_tests * sizeof(*failed_names)); void **current_state = NULL; while (current_test < number_of_tests) { const ListNode *test_check_point = NULL; TestState *current_TestState; const UnitTest * const test = &tests[current_test++]; if (!test->function) { continue; } switch (test->function_type) { case UNIT_TEST_FUNCTION_TYPE_TEST: run_next_test = 1; break; case UNIT_TEST_FUNCTION_TYPE_SETUP: { // Checkpoint the heap before the setup. current_TestState = &test_states[number_of_test_states++]; current_TestState->check_point = check_point_allocated_blocks(); test_check_point = current_TestState->check_point; current_state = ¤t_TestState->state; *current_state = NULL; run_next_test = 1; setups ++; break; } case UNIT_TEST_FUNCTION_TYPE_TEARDOWN: // Check the heap based on the last setup checkpoint. assert_true(number_of_test_states); current_TestState = &test_states[--number_of_test_states]; test_check_point = current_TestState->check_point; current_state = ¤t_TestState->state; teardowns ++; break; default: print_error("Invalid unit test function type %d\n", test->function_type); exit_test(1); break; } if (run_next_test) { int failed = _run_test(test->name, test->function, current_state, test->function_type, test_check_point); if (failed) { failed_names[total_failed] = test->name; } switch (test->function_type) { case UNIT_TEST_FUNCTION_TYPE_TEST: previous_test_failed = failed; total_failed += failed; tests_executed ++; break; case UNIT_TEST_FUNCTION_TYPE_SETUP: if (failed) { total_failed ++; tests_executed ++; // Skip forward until the next test or setup function. run_next_test = 0; } previous_test_failed = 0; break; case UNIT_TEST_FUNCTION_TYPE_TEARDOWN: // If this test failed. if (failed && !previous_test_failed) { total_failed ++; } break; default: assert_false("BUG: shouldn't be here!"); break; } } } if (total_failed) { size_t i; print_error("%d out of %d tests failed!\n", total_failed, tests_executed); for (i = 0; i < total_failed; i++) { print_error(" %s\n", failed_names[i]); } } else { print_message("All %d tests passed\n", tests_executed); } if (number_of_test_states) { print_error("Mismatched number of setup %d and teardown %d " "functions\n", setups, teardowns); total_failed = -1; } free(test_states); free((void*)failed_names); fail_if_blocks_allocated(check_point, "run_tests"); return (int)total_failed; }