/*
 * 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 <setjmp.h>
#ifndef _WIN32
#include <signal.h>
#endif // !_WIN32
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#endif // _WIN32
#include <cmockery.h>

#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 : &current_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 = &current_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 = &current_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;
}