diff --git a/src/util/list.c b/src/util/list.c new file mode 100644 index 0000000..d3e41d8 --- /dev/null +++ b/src/util/list.c @@ -0,0 +1,176 @@ +/* + ****************************************************************************** + Project: OWA EPANET + Version: 2.2 + Module: util/list.h + Description: Generic list + https://gist.github.com/pseudomuto/6334796#file-list-c + Accessed: April 9, 2019 + Authors: David Muto, Michael Tryby + Copyright: see AUTHORS + License: see LICENSE + Last Updated: 04/09/2019 + ****************************************************************************** +*/ + +#ifdef _DEBUG +#define _CRTDBG_MAP_ALLOC +#include +#include +#else +#include +#endif + +#include +#include + +#include "list.h" + + +typedef struct list_node_s { + void *data; + struct list_node_s *next; +} list_node_t; + + +typedef struct list_s { + int logicalLength; + size_t elementSize; + list_node_t *head; + list_node_t *tail; + freeFunction freeFn; +} list_t; + + +list_t *create_list(size_t elementSize, freeFunction freeFn) +{ + list_t *list; + list = (list_t *)calloc(1, sizeof(list_t)); + + assert(elementSize > 0); + list->logicalLength = 0; + list->elementSize = elementSize; + list->head = list->tail = NULL; + list->freeFn = freeFn; + return list; +} + +void delete_list(list_t *list) +{ + list_node_t *current; + + while(list->head != NULL) { + current = list->head; + list->head = current->next; + delete_node(list, current); + } + free(list); +} + +void prepend_list(list_t *list, void *element) +{ + list_node_t *node = malloc(sizeof(list_node_t)); + node->data = malloc(list->elementSize); + memcpy(node->data, element, list->elementSize); + + node->next = list->head; + list->head = node; + + // first node? + if(!list->tail) { + list->tail = list->head; + } + + list->logicalLength++; +} + +void append_list(list_t *list, void *element) +{ + list_node_t *node = malloc(sizeof(list_node_t)); + node->data = malloc(list->elementSize); + node->next = NULL; + + memcpy(node->data, element, list->elementSize); + + if(list->logicalLength == 0) { + list->head = list->tail = node; + } else { + list->tail->next = node; + list->tail = node; + } + + list->logicalLength++; +} + +void for_each_list(list_t *list, listIterator iterator) +{ + assert(iterator != NULL); + + list_node_t *node = list->head; + bool result = true; + while(node != NULL && result) { + result = iterator(node); + node = node->next; + } +} + +list_node_t *head_list(list_t *list, bool removeFromList) +// +// Warning: When node is removed caller is responsible for freeing it. +// +{ + assert(list->head != NULL); + + list_node_t *node = list->head; + if(removeFromList) { + // Disconnecting head node + list->head = node->next; + list->logicalLength--; + } + return node; +} + +list_node_t *tail_list(list_t *list) +{ + assert(list->tail != NULL); + return list->tail; +} + +int size_list(list_t *list) +{ + return list->logicalLength; +} + +void *get_data(list_node_t *lnode) +{ + return lnode->data; +} + +void delete_node(list_t *list, list_node_t *lnode) +{ + if (list->freeFn) + list->freeFn(lnode->data); + + free(lnode->data); + free(lnode); +} + +// +// Iterator first/done/next operations provide containment for list abstraction +// http://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)Iterators.html +// Accessed on April 11, 2019 +// +list_node_t *first_list(list_t *list) +{ + return list->head; +} + +bool done_list(list_node_t *lnode) +{ + return lnode != NULL; +} + +list_node_t *next_list(list_node_t *lnode) +{ + return lnode->next; +} diff --git a/src/util/list.h b/src/util/list.h new file mode 100644 index 0000000..090fa4a --- /dev/null +++ b/src/util/list.h @@ -0,0 +1,111 @@ +/* + ****************************************************************************** + Project: OWA EPANET + Version: 2.2 + Module: util/list.h + Description: Generic list + https://gist.github.com/pseudomuto/6334796#file-list-h + Accessed: April 9, 2019 + Authors: David Muto, Michael Tryby + Copyright: see AUTHORS + License: see LICENSE + Last Updated: 04/09/2019 + ****************************************************************************** +*/ + +#ifndef LIST_H +#define LIST_H + + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + + +// Forward declarations +typedef struct list_node_s list_node_t; +typedef struct list_s list_t; + +typedef void(*freeFunction)(void *); +typedef bool(*listIterator)(list_node_t *); + + +/** +@brief Initializes a linked list to store elements of elementSize and to call +freeFunction for each element when destroying a list. +*/ +list_t *create_list(size_t elementSize, freeFunction freeFn); + +/** +@brief Frees dynamically allocated nodes and optionally calls freeFunction +with each node’s data pointer. +*/ +void delete_list(list_t *list); + +/** +@brief Adds a node to the head of the list. +*/ +void prepend_list(list_t *list, void *element); + +/** +@brief Adds a node to the tail of the list. +*/ +void append_list(list_t *list, void *element); + +/** +@brief Returns the number of items in the list. +*/ +int size_list(list_t *list); + + +/** +@brief Returns pointer to list node's data. +*/ +void *get_data(list_node_t *lnode); + +/** +@brief Frees memory associated with a list node. +*/ +void delete_node(list_t *list, list_node_t *lnode); + + +/** +@brief Calls the supplied iterator function with the data element of each +node (iterates over the list). +*/ +void for_each_list(list_t *list, listIterator iterator); + +/** +@brief Returns the head of the list (optionally removing it at the same time). +*/ +list_node_t *head_list(list_t *list, bool removeFromList); + +/** +@brief Returns the tail of the list. +*/ +list_node_t *tail_list(list_t *list); + + +/** +@brief Returns list head node. +*/ +list_node_t *first_list(list_t *list); + +/** +@brief Returns true if end of list false otherwise. +*/ +bool done_list(list_node_t *lnode); + +/** +@brief Returns next node in the list. +*/ +list_node_t *next_list(list_node_t *lnode); + + +#if defined(__cplusplus) +} +#endif + +#endif /* LIST_H */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ebec8c5..0a5f91e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -72,3 +72,6 @@ add_test(NAME test_filemanager add_test(NAME test_output COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_output WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/outfile/data) + +add_test(NAME test_list + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_list) diff --git a/tests/util/CMakeLists.txt b/tests/util/CMakeLists.txt index 2965c83..59463e9 100644 --- a/tests/util/CMakeLists.txt +++ b/tests/util/CMakeLists.txt @@ -18,3 +18,9 @@ add_executable(test_filemanager ./test_filemanager.cpp ../../src/util/cstr_helper.c) target_include_directories(test_filemanager PUBLIC ../../src/) target_link_libraries(test_filemanager ${Boost_LIBRARIES}) + + +add_executable(test_list ./test_list.cpp + ../../src/util/list.c) +target_include_directories(test_list PUBLIC ../../src/) +target_link_libraries(test_list ${Boost_LIBRARIES}) diff --git a/tests/util/test_list.cpp b/tests/util/test_list.cpp new file mode 100644 index 0000000..7844e6e --- /dev/null +++ b/tests/util/test_list.cpp @@ -0,0 +1,219 @@ +/* + ****************************************************************************** + Project: OWA EPANET + Version: 2.2 + Module: util/list.h + Description: Generic list + https://gist.github.com/pseudomuto/6334796#file-sample_app-c + Accessed: April 9, 2019 + Authors: David Muto, Modified by Michael E. Tryby + Copyright: see AUTHORS + License: see LICENSE + Last Updated: 04/09/2019 + ****************************************************************************** +*/ + +#include + +#define BOOST_TEST_MODULE list +#include + +#include "util/list.h" + + +boost::test_tools::predicate_result check_string(std::string test, std::string ref) +{ + if (ref.compare(test) == 0) + return true; + else + return false; +} + +int *get_int_data(list_node_t *lnode) { + return (int *)get_data(lnode); +} + +bool iterate_int(list_node_t *lnode) +{ + printf("Found value: %d\n", *get_int_data(lnode)); + return true; +} + + +BOOST_AUTO_TEST_SUITE(test_list) + + +BOOST_AUTO_TEST_CASE(test_create_delete) { + + list_t *list; + list = create_list(sizeof(int), NULL); + + delete_list(list); +} + + +BOOST_AUTO_TEST_CASE(test_int_list){ + + int i, numbers = 10; + list_t *list = NULL; + + list = create_list(sizeof(int), NULL); + + for(i = 1; i <= numbers; i++) { + append_list(list, &i); + } + BOOST_CHECK(size_list(list) == 10); + + for_each_list(list, iterate_int); + + delete_list(list); +} + + +inline char *get_string_data(list_node_t *lnode) +{ + return *(char **)get_data(lnode); +} + +bool iterate_string(list_node_t *lnode) +{ + printf("Found string value: %s\n", get_string_data(lnode)); + return true; +} + +void free_string(void *data) +{ + free(*(char **)data); +} + +struct FixtureStrings{ + FixtureStrings() { + list = NULL; + + int numNames = 5; + const char *names[] = { "David", "Kevin", "Michael", "Craig", "Jimi" }; + + list = create_list(sizeof(char *), free_string); + + char *name; + for (int i = 0; i < numNames; i++) { + name = strdup(names[i]); + append_list(list, &name); + } + } + ~FixtureStrings() { + delete_list(list); + } + list_t *list; +}; + +BOOST_FIXTURE_TEST_CASE(test_string_list, FixtureStrings) { + + BOOST_CHECK(size_list(list) == 5); + + for_each_list(list, iterate_string); +} + + +BOOST_FIXTURE_TEST_CASE(test_head_list, FixtureStrings) { + + BOOST_CHECK(check_string(get_string_data(head_list(list, false)), "David")); + BOOST_CHECK(size_list(list) == 5); + + list_node_t *lnode = head_list(list, true); + BOOST_CHECK(check_string(get_string_data(lnode), "David")); + delete_node(list, lnode); + + BOOST_CHECK(check_string(get_string_data(head_list(list, false)), "Kevin")); + BOOST_CHECK(size_list(list) == 4); +} + + +BOOST_FIXTURE_TEST_CASE(test_tail_list, FixtureStrings) { + + BOOST_CHECK(check_string(get_string_data(tail_list(list)), "Jimi")); + BOOST_CHECK(size_list(list) == 5); +} + + +typedef struct test_data_s { + int num; + char *name; +} test_data_t; + +test_data_t *create_test_data(int number, const char *name){ + + test_data_t *data = (test_data_t *)malloc(sizeof(test_data_t)); + data->num = number; + if (name) + data->name = strdup(name); + else + data->name = NULL; + + return data; +} + +void delete_test_data(void *data) { + + test_data_t *test_data = *(test_data_t **)data; + + if (test_data->name) + free(test_data->name); + + free(test_data); +} + +inline test_data_t *get_test_data(list_node_t *lnode) +{ + return *(test_data_t **)get_data(lnode); +} + +bool iterate_test_data(list_node_t *lnode) +{ + test_data_t *test_data = get_test_data(lnode); + + printf("Found number: %i name: %s\n", test_data->num, test_data->name); + return true; +} + + + +BOOST_AUTO_TEST_CASE(test_struct_list){ + + list_t *list = NULL; + list = create_list(sizeof(test_data_t *), delete_test_data); + + + test_data_t *data = create_test_data(1, "David"); + append_list(list, &data); + + data = create_test_data(2, "Kevin"); + append_list(list, &data); + + data = create_test_data(3, "Michael"); + append_list(list, &data); + + + BOOST_CHECK(size_list(list) == 3); + + for_each_list(list, iterate_test_data); + + + list_node_t *lnode; + // Iterate over list while maintaining containment of list abstraction + for (lnode = first_list(list); done_list(lnode); lnode = next_list(lnode)) { + test_data_t *test_data = get_test_data(lnode); + + if (test_data->num == 2) + printf("Found %s!\n", test_data->name); + } + + lnode = head_list(list, true); + delete_node(list, lnode); + + delete_list(list); +} + +// TODO: search for an index and return data + +BOOST_AUTO_TEST_SUITE_END()