From: Arran Cudbard-Bell Date: Fri, 16 Dec 2016 22:32:54 +0000 (-0500) Subject: Add new generic cursor functions X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=110d5d988f272ab8ad95247889158c8a7b4abe7b;p=thirdparty%2Ffreeradius-server.git Add new generic cursor functions --- diff --git a/src/include/cursor.h b/src/include/cursor.h new file mode 100644 index 00000000000..f7fbc4cc66c --- /dev/null +++ b/src/include/cursor.h @@ -0,0 +1,136 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, cursor 2 of the + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifndef _FR_CURSOR_H +#define _FR_CURSOR_H +#include + +/** + * $Id$ + * + * @file include/cursor.h + * @brief Generic linked list cursor. + * + * @copyright 2016 The FreeRADIUS server project + */ +RCSIDH(cursor_h, "$Id$") + +/** Callback for implementing custom iterators + * + * @param[out] prev *prev != NULL, must be updated to the list item before the + * one returned in to_eval. + * @param[out] to_eval the next item in the list. Iterator should check to + * see if it matches the iterator's filter, and if it doesn't + * iterate over the items until one if found that does. + * @param[in] ctx passed to #fr_cursor_init. + */ +typedef void (*fr_cursor_iter_t)(void **prev, void **to_eval, void *ctx); + +typedef struct fr_cursor_s { + void **head; //!< First item in the list. + void *tail; //!< Used for efficient fr_cursor_append. + void *current; //!< The current item. + void *prev; //!< The previous item. + + size_t offset; //!< Where the next ptr is in the item struct. + fr_cursor_iter_t iter; //!< Iterator function. + void *ctx; //!< to pass to iterator function. + char const *type; //!< If set, used for explicit runtime type safety checks. +} fr_cursor_t; + +void fr_cursor_copy(fr_cursor_t *out, fr_cursor_t *in); + +void *fr_cursor_head(fr_cursor_t *cursor); + +void *fr_cursor_tail(fr_cursor_t *cursor); + +void *fr_cursor_next(fr_cursor_t *cursor); + +void *fr_cursor_next_peek(fr_cursor_t *cursor); + +void *fr_cursor_list_next_peek(fr_cursor_t *cursor); + +void *fr_cursor_list_prev_peek(fr_cursor_t *cursor); + +void *fr_cursor_current(fr_cursor_t *cursor); + +void fr_cursor_prepend(fr_cursor_t *cursor, void *v); + +void fr_cursor_append(fr_cursor_t *cursor, void *v); + +void fr_cursor_insert(fr_cursor_t *cursor, void *v); + +void fr_cursor_merge(fr_cursor_t *cursor, fr_cursor_t *to_append); + +void *fr_cursor_remove(fr_cursor_t *cursor); + +void *fr_cursor_replace(fr_cursor_t *cursor, void *r); + +void fr_cursor_list_free(fr_cursor_t *cursor); + +/** Initialise a cursor with runtime talloc type safety checks and a custom iterator + * + * @param[in] _cursor to initialise. + * @param[in] _head of item list. + * @param[in] _iter function. + * @param[in] _ctx _iter function _ctx. + * @param[in] _type Talloc type i.e. VALUE_PAIR or value_box_t. + * @return + * - NULL if _head does not point to any items, or the iterator matches no items + * in the current list. + * - The first item returned by the iterator. + */ +#define fr_cursor_talloc_iter_init(_cursor, _head, _iter, _ctx, _type) \ + _fr_cursor_init(_cursor, (void **)_head, offsetof(typeof(**_head), next), _iter, _ctx, #_type) + +/** Initialise a cursor with a custom iterator + * + * @param[in] _cursor to initialise. + * @param[in] _head of item list. + * @param[in] _iter function. + * @param[in] _ctx _iter function _ctx. + * @return + * - NULL if _head does not point to any items, or the iterator matches no items + * in the current list. + * - The first item returned by the iterator. + */ +#define fr_cursor_iter_init(_cursor, _head, _iter, _ctx) \ + _fr_cursor_init(_cursor, (void **)_head, offsetof(typeof(**_head), next), _iter, _ctx, NULL) + +/** Initialise a cursor with runtime talloc type safety checks + * + * @param[in] _cursor to initialise. + * @param[in] _head of item list. + * @param[in] _type Talloc type i.e. VALUE_PAIR or value_box_t. + * @return + * - NULL if _head does not point to any items. + * - The first item in the list. + */ +#define fr_cursor_talloc_init(_cursor, _head, _type) \ + _fr_cursor_init(_cursor, (void **)_head, offsetof(typeof(**_head), next), NULL, NULL, #_type) + +/** Initialise a cursor with runtime talloc type safety checks + * + * @param[in] _cursor to initialise. + * @param[in] _head of item list. + * @return + * - NULL if _head does not point to any items. + * - The first item in the list. + */ +#define fr_cursor_init(_cursor, _head) \ + _fr_cursor_init(_cursor, (void **)_head, offsetof(typeof(**_head), next), NULL, NULL, NULL) + +void *_fr_cursor_init(fr_cursor_t *cursor, void * const *head, size_t offset, + fr_cursor_iter_t iter, void const *ctx, char const *type); +#endif diff --git a/src/include/cutest.h b/src/include/cutest.h new file mode 100644 index 00000000000..b2a24566c48 --- /dev/null +++ b/src/include/cutest.h @@ -0,0 +1,657 @@ +/* + * CUTest -- C/C++ Unit Test facility + * + * + * Copyright (c) 2013-2016 Martin Mitas + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef CUTEST_H__ +#define CUTEST_H__ + + +/************************ + *** Public interface *** + ************************/ + +/* By default, provides the main program entry point (function + * main()). However, if the test suite is composed of multiple source files + * which include , then this causes a problem of multiple main() + * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all + * compilation units but one. + */ + +/* Macro to specify list of unit tests in the suite. + * The unit test implementation MUST provide list of unit tests it implements + * with this macro: + * + * TEST_LIST = { + * { "test1_name", test1_func_ptr }, + * { "test2_name", test2_func_ptr }, + * ... + * { 0 } + * }; + * + * The list specifies names of each test (must be unique) and pointer to + * a function implementing it. The function does not take any arguments + * and has no return values, i.e. every test function has tp be compatible + * with this prototype: + * + * void test_func(void); + */ +#define TEST_LIST const struct test__ test_list__[] + + +/* Macros for testing whether an unit test succeeds or fails. These macros + * can be used arbitrarily in functions implementing the unit tests. + * + * If any condition fails throughout execution of a test, the test fails. + * + * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows + * also to specify an error message to print out if the condition fails. + * (It expects printf-like format string and its parameters). The macros + * return non-zero (condition passes) or 0 (condition fails). + * + * That can be useful when more conditions should be checked only if some + * preceding condition passes, as illustrated in this code snippet: + * + * SomeStruct* ptr = allocate_some_struct(); + * if(TEST_CHECK(ptr != NULL)) { + * TEST_CHECK(ptr->member1 < 100); + * TEST_CHECK(ptr->member2 > 200); + * } + */ +#define TEST_CHECK_(cond,...) test_check__((cond), __FILE__, __LINE__, __VA_ARGS__) +#define TEST_CHECK(cond) test_check__((cond), __FILE__, __LINE__, "%s", #cond) + + +/********************** + *** Implementation *** + **********************/ + +/* The unit test files should not rely on anything below. */ + + +#if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__) + #define CUTEST_UNIX__ 1 + /* CUTEST_UNIX__ assumes POSIX.1-1990 or later is available */ + #ifndef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 1 + #endif + #include + #include + #include + #include + #include +#endif + +/* _POSIX_C_SOURCE must be defined before these includes */ +#include +#include +#include +#include +#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define CUTEST_WIN__ 1 + #include + #include +#endif + +#ifdef __cplusplus + #include +#endif + + +/* Note our global private identifiers end with '__' to mitigate risk of clash + * with the unit tests implementation. */ + + +#ifdef __cplusplus + extern "C" { +#endif + + +struct test__ { + const char* name; + void (*func)(void); +}; + +extern const struct test__ test_list__[]; +extern int test_verbose_level__; +extern const struct test__* test_current_unit__; +extern int test_current_already_logged__; +extern int test_current_failures__; +extern int test_colorize__; + + +#define CUTEST_COLOR_DEFAULT__ 0 +#define CUTEST_COLOR_GREEN__ 1 +#define CUTEST_COLOR_RED__ 2 +#define CUTEST_COLOR_DEFAULT_INTENSIVE__ 3 +#define CUTEST_COLOR_GREEN_INTENSIVE__ 4 +#define CUTEST_COLOR_RED_INTENSIVE__ 5 + +size_t +test_print_in_color__(int color, const char* fmt, ...) +{ + va_list args; + char buffer[256]; + size_t n; + + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + buffer[sizeof(buffer)-1] = '\0'; + + if(!test_colorize__) { + return printf("%s", buffer); + } + +#if defined CUTEST_UNIX__ + { + const char* col_str; + switch(color) { + case CUTEST_COLOR_GREEN__: col_str = "\033[0;32m"; break; + case CUTEST_COLOR_RED__: col_str = "\033[0;31m"; break; + case CUTEST_COLOR_GREEN_INTENSIVE__: col_str = "\033[1;32m"; break; + case CUTEST_COLOR_RED_INTENSIVE__: col_str = "\033[1;30m"; break; + case CUTEST_COLOR_DEFAULT_INTENSIVE__: col_str = "\033[1m"; break; + default: col_str = "\033[0m"; break; + } + printf("%s", col_str); + n = printf("%s", buffer); + printf("\033[0m"); + return n; + } +#elif defined CUTEST_WIN__ + { + HANDLE h; + CONSOLE_SCREEN_BUFFER_INFO info; + WORD attr; + + h = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(h, &info); + + switch(color) { + case CUTEST_COLOR_GREEN__: attr = FOREGROUND_GREEN; break; + case CUTEST_COLOR_RED__: attr = FOREGROUND_RED; break; + case CUTEST_COLOR_GREEN_INTENSIVE__: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; + case CUTEST_COLOR_RED_INTENSIVE__: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break; + case CUTEST_COLOR_DEFAULT_INTENSIVE__: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break; + default: attr = 0; break; + } + if(attr != 0) + SetConsoleTextAttribute(h, attr); + n = printf("%s", buffer); + SetConsoleTextAttribute(h, info.wAttributes); + return n; + } +#else + n = printf("%s", buffer); + return n; +#endif +} + +int +test_check__(int cond, const char* file, int line, const char* fmt, ...) +{ + const char *result_str; + int result_color; + int verbose_level; + + if(cond) { + result_str = "ok"; + result_color = CUTEST_COLOR_GREEN__; + verbose_level = 3; + } else { + if(!test_current_already_logged__ && test_current_unit__ != NULL) { + printf("[ "); + test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, "FAILED"); + printf(" ]\n"); + } + result_str = "failed"; + result_color = CUTEST_COLOR_RED__; + verbose_level = 2; + test_current_failures__++; + test_current_already_logged__++; + } + + if(test_verbose_level__ >= verbose_level) { + size_t n = 0; + va_list args; + + printf(" "); + + if(file != NULL) + n += printf("%s:%d: Check ", file, line); + + va_start(args, fmt); + n += vprintf(fmt, args); + va_end(args); + + printf("... "); + test_print_in_color__(result_color, result_str); + printf("\n"); + test_current_already_logged__++; + } + + return (cond != 0); +} + + +#ifndef TEST_NO_MAIN + +static char* test_argv0__ = NULL; +static int test_count__ = 0; +static int test_no_exec__ = 0; +static int test_no_summary__ = 0; +static int test_skip_mode__ = 0; + +static int test_stat_failed_units__ = 0; +static int test_stat_run_units__ = 0; + +const struct test__* test_current_unit__ = NULL; +int test_current_already_logged__ = 0; +int test_verbose_level__ = 2; +int test_current_failures__ = 0; +int test_colorize__ = 0; + + +static void +test_list_names__(void) +{ + const struct test__* test; + + printf("Unit tests:\n"); + for(test = &test_list__[0]; test->func != NULL; test++) + printf(" %s\n", test->name); +} + +static const struct test__* +test_by_name__(const char* name) +{ + const struct test__* test; + + for(test = &test_list__[0]; test->func != NULL; test++) { + if(strcmp(test->name, name) == 0) + return test; + } + + return NULL; +} + +/* Call directly the given test unit function. */ +static int +test_do_run__(const struct test__* test) +{ + test_current_unit__ = test; + test_current_failures__ = 0; + test_current_already_logged__ = 0; + + if(test_verbose_level__ >= 3) { + test_print_in_color__(CUTEST_COLOR_DEFAULT_INTENSIVE__, "Test %s:\n", test->name); + test_current_already_logged__++; + } else if(test_verbose_level__ >= 1) { + size_t n; + char spaces[32]; + + n = test_print_in_color__(CUTEST_COLOR_DEFAULT_INTENSIVE__, "Test %s... ", test->name); + memset(spaces, ' ', sizeof(spaces)); + if(n < sizeof(spaces)) + printf("%.*s", (int) (sizeof(spaces) - n), spaces); + } else { + test_current_already_logged__ = 1; + } + +#ifdef __cplusplus + try { +#endif + + /* This is good to do for case the test unit e.g. crashes. */ + fflush(stdout); + fflush(stderr); + + test->func(); + +#ifdef __cplusplus + } catch(std::exception& e) { + const char* what = e.what(); + if(what != NULL) + test_check__(0, NULL, 0, "Threw std::exception: %s", what); + else + test_check__(0, NULL, 0, "Threw std::exception"); + } catch(...) { + test_check__(0, NULL, 0, "Threw an exception"); + } +#endif + + if(test_verbose_level__ >= 3) { + switch(test_current_failures__) { + case 0: test_print_in_color__(CUTEST_COLOR_GREEN_INTENSIVE__, " All conditions have passed.\n\n"); break; + case 1: test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, " One condition has FAILED.\n\n"); break; + default: test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, " %d conditions have FAILED.\n\n", test_current_failures__); break; + } + } else if(test_verbose_level__ >= 1 && test_current_failures__ == 0) { + printf("[ "); + test_print_in_color__(CUTEST_COLOR_GREEN_INTENSIVE__, "OK"); + printf(" ]\n"); + } + + test_current_unit__ = NULL; + return (test_current_failures__ == 0) ? 0 : -1; +} + +/* Called if anything goes bad in cutest, or if the unit test ends in other + * way then by normal returning from its function (e.g. exception or some + * abnormal child process termination). */ +static void +test_error__(const char* fmt, ...) +{ + va_list args; + + if(test_verbose_level__ == 0) + return; + + if(test_verbose_level__ <= 2 && !test_current_already_logged__ && test_current_unit__ != NULL) { + printf("[ "); + test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, "FAILED"); + printf(" ]\n"); + } + + if(test_verbose_level__ >= 2) { + test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, " Error: "); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + printf("\n"); + } +} + +/* Trigger the unit test. If possible (and not suppressed) it starts a child + * process who calls test_do_run__(), otherwise it calls test_do_run__() + * directly. */ +static void +test_run__(const struct test__* test) +{ + int failed = 1; + + test_current_unit__ = test; + test_current_already_logged__ = 0; + + if(!test_no_exec__) { + +#if defined(CUTEST_UNIX__) + + pid_t pid; + int exit_code; + + pid = fork(); + if(pid == (pid_t)-1) { + test_error__("Cannot fork. %s [%d]", strerror(errno), errno); + failed = 1; + } else if(pid == 0) { + /* Child: Do the test. */ + failed = (test_do_run__(test) != 0); + exit(failed ? 1 : 0); + } else { + /* Parent: Wait until child terminates and analyze its exit code. */ + waitpid(pid, &exit_code, 0); + if(WIFEXITED(exit_code)) { + switch(WEXITSTATUS(exit_code)) { + case 0: failed = 0; break; /* test has passed. */ + case 1: /* noop */ break; /* "normal" failure. */ + default: test_error__("Unexpected exit code [%d]", WEXITSTATUS(exit_code)); + } + } else if(WIFSIGNALED(exit_code)) { + char tmp[32]; + const char* signame; + switch(WTERMSIG(exit_code)) { + case SIGINT: signame = "SIGINT"; break; + case SIGHUP: signame = "SIGHUP"; break; + case SIGQUIT: signame = "SIGQUIT"; break; + case SIGABRT: signame = "SIGABRT"; break; + case SIGKILL: signame = "SIGKILL"; break; + case SIGSEGV: signame = "SIGSEGV"; break; + case SIGILL: signame = "SIGILL"; break; + case SIGTERM: signame = "SIGTERM"; break; + default: sprintf(tmp, "signal %d", WTERMSIG(exit_code)); signame = tmp; break; + } + test_error__("Test interrupted by %s", signame); + } else { + test_error__("Test ended in an unexpected way [%d]", exit_code); + } + } + +#elif defined(CUTEST_WIN__) + + char buffer[512] = {0}; + STARTUPINFOA startupInfo = {0}; + PROCESS_INFORMATION processInfo; + DWORD exitCode; + + /* Windows has no fork(). So we propagate all info into the child + * through a command line arguments. */ + _snprintf(buffer, sizeof(buffer)-1, + "%s --no-exec --no-summary --verbose=%d --color=%s -- \"%s\"", + test_argv0__, test_verbose_level__, + test_colorize__ ? "always" : "never", test->name); + startupInfo.cb = sizeof(STARTUPINFO); + if(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) { + WaitForSingleObject(processInfo.hProcess, INFINITE); + GetExitCodeProcess(processInfo.hProcess, &exitCode); + CloseHandle(processInfo.hThread); + CloseHandle(processInfo.hProcess); + failed = (exitCode != 0); + } else { + test_error__("Cannot create unit test subprocess [%ld].", GetLastError()); + failed = 1; + } + +#else + + /* A platform where we don't know how to run child process. */ + failed = (test_do_run__(test) != 0); + +#endif + + } else { + /* Child processes suppressed through --no-exec. */ + failed = (test_do_run__(test) != 0); + } + + test_current_unit__ = NULL; + + test_stat_run_units__++; + if(failed) + test_stat_failed_units__++; +} + +#if defined(CUTEST_WIN__) +/* Callback for SEH events. */ +static LONG CALLBACK +test_exception_filter__(EXCEPTION_POINTERS *ptrs) +{ + test_error__("Unhandled SEH exception %08lx at %p.", + ptrs->ExceptionRecord->ExceptionCode, + ptrs->ExceptionRecord->ExceptionAddress); + fflush(stdout); + fflush(stderr); + return EXCEPTION_EXECUTE_HANDLER; +} +#endif + +static void +test_help__(void) +{ + printf("Usage: %s [options] [test...]\n", test_argv0__); + printf("Run the specified unit tests; or if the option '--skip' is used, run all\n"); + printf("tests in the suite but those listed. By default, if no tests are specified\n"); + printf("on the command line, all unit tests in the suite are run.\n"); + printf("\n"); + printf("Options:\n"); + printf(" -s, --skip Execute all unit tests but the listed ones\n"); + printf(" --no-exec Do not execute unit tests as child processes\n"); + printf(" --no-summary Suppress printing of test results summary\n"); + printf(" -l, --list List unit tests in the suite and exit\n"); + printf(" -v, --verbose Enable more verbose output\n"); + printf(" --verbose=LEVEL Set verbose level to LEVEL:\n"); + printf(" 0 ... Be silent\n"); + printf(" 1 ... Output one line per test (and summary)\n"); + printf(" 2 ... As 1 and failed conditions (this is default)\n"); + printf(" 3 ... As 1 and all conditions (and extended summary)\n"); + printf(" --color=WHEN Enable colorized output (WHEN is one of 'auto', 'always', 'never')\n"); + printf(" -h, --help Display this help and exit\n"); + printf("\n"); + test_list_names__(); +} + +int +main(int argc, char** argv) +{ + const struct test__** tests = NULL; + int i, j, n = 0; + int seen_double_dash = 0; + + test_argv0__ = argv[0]; + +#if defined CUTEST_UNIX__ + test_colorize__ = isatty(fileno(stdout)); +#elif defined CUTEST_WIN__ + test_colorize__ = _isatty(_fileno(stdout)); +#else + test_colorize__ = 0; +#endif + + /* Parse options */ + for(i = 1; i < argc; i++) { + if(seen_double_dash || argv[i][0] != '-') { + tests = (const struct test__**) realloc((void*)tests, (n+1) * sizeof(const struct test__*)); + if(tests == NULL) { + fprintf(stderr, "Out of memory.\n"); + exit(2); + } + tests[n] = test_by_name__(argv[i]); + if(tests[n] == NULL) { + fprintf(stderr, "%s: Unrecognized unit test '%s'\n", argv[0], argv[i]); + fprintf(stderr, "Try '%s --list' for list of unit tests.\n", argv[0]); + exit(2); + } + n++; + } else if(strcmp(argv[i], "--") == 0) { + seen_double_dash = 1; + } else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + test_help__(); + exit(0); + } else if(strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) { + test_verbose_level__++; + } else if(strncmp(argv[i], "--verbose=", 10) == 0) { + test_verbose_level__ = atoi(argv[i] + 10); + } else if(strcmp(argv[i], "--color=auto") == 0) { + /* noop (set from above) */ + } else if(strcmp(argv[i], "--color=always") == 0 || strcmp(argv[i], "--color") == 0) { + test_colorize__ = 1; + } else if(strcmp(argv[i], "--color=never") == 0) { + test_colorize__ = 0; + } else if(strcmp(argv[i], "--skip") == 0 || strcmp(argv[i], "-s") == 0) { + test_skip_mode__ = 1; + } else if(strcmp(argv[i], "--no-exec") == 0) { + test_no_exec__ = 1; + } else if(strcmp(argv[i], "--no-summary") == 0) { + test_no_summary__ = 1; + } else if(strcmp(argv[i], "--list") == 0 || strcmp(argv[i], "-l") == 0) { + test_list_names__(); + exit(0); + } else { + fprintf(stderr, "%s: Unrecognized option '%s'\n", argv[0], argv[i]); + fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); + exit(2); + } + } + +#if defined(CUTEST_WIN__) + SetUnhandledExceptionFilter(test_exception_filter__); +#endif + + /* Count all test units */ + test_count__ = 0; + for(i = 0; test_list__[i].func != NULL; i++) + test_count__++; + + /* Run the tests */ + if(n == 0) { + /* Run all tests */ + for(i = 0; test_list__[i].func != NULL; i++) + test_run__(&test_list__[i]); + } else if(!test_skip_mode__) { + /* Run the listed tests */ + for(i = 0; i < n; i++) + test_run__(tests[i]); + } else { + /* Run all tests except those listed */ + int is_skipped; + + for(i = 0; test_list__[i].func != NULL; i++) { + is_skipped = 0; + for(j = 0; j < n; j++) { + if(tests[j] == &test_list__[i]) { + is_skipped = 1; + break; + } + } + if(!is_skipped) + test_run__(&test_list__[i]); + } + } + + /* Write a summary */ + if(!test_no_summary__ && test_verbose_level__ >= 1) { + test_print_in_color__(CUTEST_COLOR_DEFAULT_INTENSIVE__, "\nSummary:\n"); + + if(test_verbose_level__ >= 3) { + printf(" Count of all unit tests: %4d\n", test_count__); + printf(" Count of run unit tests: %4d\n", test_stat_run_units__); + printf(" Count of failed unit tests: %4d\n", test_stat_failed_units__); + printf(" Count of skipped unit tests: %4d\n", test_count__ - test_stat_run_units__); + } + + if(test_stat_failed_units__ == 0) { + test_print_in_color__(CUTEST_COLOR_GREEN_INTENSIVE__, + " SUCCESS: All unit tests have passed.\n"); + } else { + test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, + " FAILED: %d of %d unit tests have failed.\n", + test_stat_failed_units__, test_stat_run_units__); + } + } + + if(tests != NULL) + free((void*)tests); + + return (test_stat_failed_units__ == 0) ? 0 : 1; +} + + +#endif /* #ifndef TEST_NO_MAIN */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif + + +#endif /* #ifndef CUTEST_H__ */ diff --git a/src/lib/cursor.c b/src/lib/cursor.c new file mode 100644 index 00000000000..c4cda356e2c --- /dev/null +++ b/src/lib/cursor.c @@ -0,0 +1,1768 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 of the + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file lib/cursor.c + * @brief Functions to iterate over a sets and subsets of items. + * + * @note Do not modify collections of items pointed to by a cursor + * with none fr_cursor_* functions over the lifetime of that cursor. + * + * @author Arran Cudbard-Bell + * @copyright 2013-2016 Arran Cudbard-Bell + * @copyright 2013-2016 The FreeRADIUS Server Project. + */ + +#include +#include +#include + +#define NEXT_PTR(_v) ((void **)(((uint8_t *)(_v)) + cursor->offset)) + +/** Internal function to get the next attribute + * + * @param[in, out] prev attribute to the one we returned. May be NULL. + * @param[in] cursor to operate on. + * @param[in] current attribute. + * @return + * - The next attribute. + * - NULL if no more attributes. + */ +static inline void *cursor_next(void **prev, fr_cursor_t *cursor, void *current) +{ + void *unused = NULL; + + if (!prev) prev = &unused; + + /* + * First time next has been called + */ + if (!current) { + if (!*(cursor->head)) return NULL; + if (cursor->prev) return NULL; /* At tail of the list */ + if (!cursor->iter) return (*cursor->head); /* Fast path without custom iter */ + + current = *cursor->head; + cursor->iter(prev, ¤t, cursor->ctx); + return current; + } + + if (!cursor->iter) { + void *next; + + next = *NEXT_PTR(current); /* Fast path without custom iter */ + if (prev) *prev = current; + + return next; + } + + cursor->iter(prev, *NEXT_PTR(current), cursor->ctx); + + return current; +} + +/** Internal function to get the last attribute + * + * @param[in, out] prev attribute to the one we returned. May be NULL. + * @param[in] cursor to operate on. + * @param[in] current attribute. + * @return the last attribute. + */ +static inline void *cursor_tail(void **prev, fr_cursor_t *cursor, void *current) +{ + void *v, *nv, *p, *np; + void *unused = NULL; + + if (!prev) prev = &unused; + if (current) { + nv = v = current; + np = p = *prev; + /* + * When hunting for the tail we're allowed + * to wrap around to the start of the list. + */ + } else { + nv = v = *cursor->head; + np = p = NULL; + } + + while ((nv = cursor_next(&np, cursor, nv))) { + v = nv; /* Wind to the end */ + p = np; + } + + *prev = p; + + return v; +} + +/** Copy cursor parameters and state. + * + * @param[out] out Where to copy the cursor to. + * @param[in] in cursor to copy. + */ +void fr_cursor_copy(fr_cursor_t *out, fr_cursor_t *in) +{ + memcpy(out, in, sizeof(*out)); +} + +/** Rewind cursor to the start of the list + * + * @param[in] cursor to operate on. + * @return item at the start of the list. + */ +void *fr_cursor_head(fr_cursor_t *cursor) +{ + if (!cursor->head) return NULL; + + cursor->current = *cursor->head; + cursor->prev = NULL; + + return cursor->current; +} + +/** Wind cursor to the tail item in the list + * + * @param[in] cursor to operate on. + * @return item at the end of the list. + */ +void *fr_cursor_tail(fr_cursor_t *cursor) +{ + if (!cursor->head || !*cursor->head) return NULL; + + cursor->current = cursor_tail(&cursor->prev, cursor, cursor->current); + cursor->tail = cursor->current; /* May as well update our insertion tail */ + + return cursor->current; +} + +/** Advanced the cursor to the next item + * + * @param cursor to operate on. + * @return + * - Next item + * - NULL if no more #void in the collection. + */ +void *fr_cursor_next(fr_cursor_t *cursor) +{ + if (!cursor->head || !*cursor->head) return NULL; + + cursor->current = cursor_next(&cursor->prev, cursor, cursor->current); + + return cursor->current; +} + +/** Return the next iterator item without advancing the cursor + * + * @param cursor to operate on. + * @return + * - Next #void. + * - NULL if no more #void are in the collection. + */ +void *fr_cursor_next_peek(fr_cursor_t *cursor) +{ + return cursor_next(NULL, cursor, cursor->current); +} + +/** Returns the next list item without advancing the cursor + * + * @note This returns the next item in the list, which may not match the + * next iterator value. It's mostly used for debugging. You probably + * want #fr_cursor_next_peek. + * + * @param[in] cursor to operator on. + * @return + * - Next item in list. + * - NULL if no next item available. + */ + void *fr_cursor_list_next_peek(fr_cursor_t *cursor) +{ + if (!cursor->current) return NULL; + + return *NEXT_PTR(cursor->current); +} + +/** Returns the previous list item without rewinding the cursor + * + * @note This returns the previous item in the list, which may not be the + * previous 'current' value. + * + * @param[in] cursor to operator on. + * @return + * - Previous item. + * - NULL if no previous item available. + */ +void *fr_cursor_list_prev_peek(fr_cursor_t *cursor) +{ + return cursor->prev; +} + +/** Return the item the cursor current points to + * + * @param[in] cursor to operate on. + * @return the item the cursor currently points to. + */ +void *fr_cursor_current(fr_cursor_t *cursor) +{ + return cursor->current; +} + +/** Insert a single item at the start of the list + * + * @note Will not advance cursor position to r attribute, but will set cursor + * to this attribute, if it's the head one in the list. + * + * Insert a void at the start of the list. + * + * @param cursor to operate on. + * @param v to insert. + */ +void fr_cursor_prepend(fr_cursor_t *cursor, void *v) +{ + void *old; + + if (!cursor->head) return; /* cursor must have been initialised */ + + if (!v) return; + + /* + * Cursor was initialised with a pointer to a NULL item + */ + if (!*(cursor->head)) { + *cursor->head = v; + cursor->tail = *cursor->head; + + *NEXT_PTR(v) = NULL; /* Only insert one at a time */ + + fr_cursor_next(cursor); /* Update current */ + + return; + } + + /* + * Insert at the head of the list + */ + old = *(cursor->head); + *cursor->head = v; + *NEXT_PTR(v) = old; + + if (!cursor->prev) cursor->prev = v; +} + +/** Insert a single item at the end of the list + * + * @note If the cursor already advanced + * + * @param cursor to operate on. + * @param v to insert. + */ +void fr_cursor_append(fr_cursor_t *cursor, void *v) +{ + void *old; + + if (!cursor->head) return; /* cursor must have been initialised */ + if (!v) return; + + /* + * Cursor was initialised with a pointer to a NULL item + */ + if (!*(cursor->head)) { + *cursor->head = v; + *NEXT_PTR(v) = NULL; /* Only insert one at a time */ + + fr_cursor_next(cursor); /* Update current */ + + return; + } + + /* + * Wind to the end (not updating current) + */ + cursor->tail = cursor_tail(NULL, cursor, cursor->tail); + + /* + * Some weirdness here... The intent of the iterator functions + * is to iterate over subsets of the list. + * + * This means although fr_cursor_tail has wound to the end of + * this subset of the list, there could still be items *after* + * the end of this subset, so we still need to link them in. + */ + old = *NEXT_PTR(cursor->tail); + *NEXT_PTR(cursor->tail) = v; + *NEXT_PTR(v) = old; + + cursor->tail = v; +} + +/** Insert directly after the current item + * + * @param[in] cursor to operate on. + * @param[in] v Item to insert. + */ +void fr_cursor_insert(fr_cursor_t *cursor, void *v) +{ + void *old; + + if (!cursor->current) { + fr_cursor_append(cursor, v); + return; + } + + old = *NEXT_PTR(cursor->current); + *NEXT_PTR(cursor->current) = v; + *NEXT_PTR(v) = old; + + if (cursor->tail == cursor->current) cursor->tail = v; /* Advance the tail */ +} + +/** Appends items from one cursor to another. + * + * Append multiple items from one cursor to another. + * + * @note Will only append items from the current position of to_append + * to the end of to_append. Items will be removed from the original + * cursor. + * + * @param[in] cursor to operate on. + * @param[in] to_append Items to append. + */ +void fr_cursor_merge(fr_cursor_t *cursor, fr_cursor_t *to_append) +{ + void *v, *t; + + if (!to_append) return; + if (!cursor->head || !to_append->head) return; /* cursor must have been initialised */ + + v = fr_cursor_current(to_append); + if (!v) return; + + t = cursor_tail(NULL, cursor, cursor->current); + if (t) { + *NEXT_PTR(t) = v; + } else { + *(cursor->head) = v; + } + + /* + * Fixup from cursor + */ + if (to_append->prev) *NEXT_PTR(to_append->prev) = NULL; + to_append->current = NULL; + if (to_append->tail == v) to_append->tail = to_append->prev; +} + +/** Remove the current item + * + * The current item will be set to the one before the item being removed, + * this is so the commonly used check and remove loop (below) works as expected. + * + @code {.c} + for (v = fr_cursor_init(&cursor, head); + v; + v = fr_cursor_next(&cursor) { + if () { + v = fr_cursor_remove(&cursor); + talloc_free(v); + } + } + @endcode + * + * @param cursor to remove the current item from. + * @return + * - item we just removed. + * - NULL on error. + */ +void *fr_cursor_remove(fr_cursor_t *cursor) +{ + void *v, *p; + + if (!cursor->head) return NULL; /* cursor must have been initialised */ + if (!cursor->current) return NULL; /* don't do anything fancy, it's just a noop */ + + v = cursor->current; + p = cursor->prev; + + if (*cursor->head == v) { + *cursor->head = *NEXT_PTR(v); /* at the start (make next head)*/ + cursor->current = NULL; + } else { + *NEXT_PTR(p) = *NEXT_PTR(v); /* in the middle/end (unlink) */ + cursor->current = p; + } + cursor->prev = NULL; + + /* + * Fixup append pointer. + */ + if (cursor->tail == v) { + void *n; + + n = cursor_next(NULL, cursor, v); + if (n) { + cursor->tail = n; /* advance tail to the one we removed */ + } else if (p) { + cursor->tail = p; /* if the one we removed was the end, tail is prev */ + } else { + cursor->tail = *(cursor->head); /* if no prev, tail is set to head (wrap) */ + } + } + + /* + * re-advance the cursor. + * + * This ensures if the iterator skips the item + * we just replaced, it doesn't become current. + */ + fr_cursor_next(cursor); + + /* + * Set v->next to NULL + */ + *NEXT_PTR(v) = NULL; + + return v; +} + +/** Replace the current item + * + * After replacing the current item, the cursor will be rewound, + * and the next item selected by the iterator function will become current. + * + * @param cursor to replace the current item in. + * @param r #void to insert. + * @return + * - #void we just replaced. + * - NULL on error. + */ +void *fr_cursor_replace(fr_cursor_t *cursor, void *r) +{ + void *v, *p; + + if (!cursor->head) return NULL; /* cursor must have been initialised */ + + /* + * Correct behaviour here is debatable + */ + if (!*cursor->head) { + fr_cursor_prepend(cursor, r); + return NULL; + } + + v = cursor->current; + p = cursor->prev; + + /* + * ...item must be at the head of the list. + */ + if (*cursor->head == v) { + *cursor->head = r; + *NEXT_PTR(r) = *NEXT_PTR(v); + } else { + *NEXT_PTR(p) = r; + *NEXT_PTR(r) = *NEXT_PTR(v); + } + + /* + * Fixup current pointer. + */ + if (cursor->current) { + cursor->current = p; + cursor->prev = NULL; /* populated on next call to fr_cursor_next */ + } + + /* + * Fixup tail pointer. + */ + if (cursor->tail == v) cursor->tail = r; /* set tail to the replacement */ + + /* + * re-advance the cursor. + * + * This ensures if the iterator skips the item + * we just replaced, it doesn't become current. + */ + fr_cursor_next(cursor); + + /* + * Set v->next to NULL + */ + *NEXT_PTR(v) = NULL; + + return v; +} + +/** Free the current item and all items after it + * + * @note Use fr_cursor_remove and talloc_free to free single items. + * + * Current should be the item *after* the one freed. + * + * @param cursor to free items in. + */ +void fr_cursor_list_free(fr_cursor_t *cursor) +{ + void *v; + + if (!*(cursor->head)) return; /* noop */ + + do { + v = fr_cursor_remove(cursor); + talloc_free(v); + } while (v); +} + +/** Setup a cursor to iterate over attribute items + * + * @param[in] cursor Where to initialise the cursor (uses existing structure). + * @param[in] head to start from. + * @param[in] offset offsetof next ptr in the structure we're iterating over. + * @param[in] iter Iterator callback. + * @param[in] ctx to pass to iterator function. + * @param[in] type if iterating over talloced memory. + * @return the attribute pointed to by v. + */ +void *_fr_cursor_init(fr_cursor_t *cursor, void * const *head, size_t offset, + fr_cursor_iter_t iter, void const *ctx, char const *type) +{ + void **v; + + if (!head || !cursor) return NULL; + + memcpy(&v, &head, sizeof(v)); /* stupid const hacks */ + + cursor->head = v; + cursor->tail = *v; + cursor->prev = cursor->current = NULL; + cursor->iter = iter; + cursor->offset = offset; + memcpy(&cursor->ctx, &ctx, sizeof(cursor->ctx)); + + if (*head) return fr_cursor_next(cursor); /* Initialise current */ + + return NULL; +} + +#ifdef TESTING +/* + * cc cursor.c -g3 -Wall -DTESTING -I../include -l talloc -o test_cursor && ./test_cursor + */ +#include +#include + +typedef struct { + char const *name; + void *next; +} test_item_t; + +static void test_iter(void **prev, void **current, void *ctx) +{ + return; +} + +/** Verify internal state is initialised correctly + * + */ +void test_init_null_item(void) +{ + fr_cursor_t cursor; + test_item_t *item_p; + test_item_t *head = NULL; + + item_p = fr_cursor_iter_init(&cursor, &head, test_iter, &cursor); + TEST_CHECK(!item_p); + TEST_CHECK((*cursor.head) == head); + TEST_CHECK(!cursor.tail); + TEST_CHECK(!fr_cursor_current(&cursor)); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + TEST_CHECK(!fr_cursor_list_next_peek(&cursor)); + TEST_CHECK(cursor.ctx == &cursor); +} + +void test_init_1i_start(void) +{ + fr_cursor_t cursor; + test_item_t item1 = { "item1", NULL }; + test_item_t *item_p; + test_item_t *head = &item1; + + item_p = fr_cursor_init(&cursor, &head); + TEST_CHECK(item_p == &item1); + TEST_CHECK((*cursor.head) == head); + TEST_CHECK(fr_cursor_current(&cursor) == &item1); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); +} + +void test_init_2i_start(void) +{ + fr_cursor_t cursor; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + item_p = fr_cursor_init(&cursor, &head); + TEST_CHECK(item_p == &item1); + TEST_CHECK(fr_cursor_current(&cursor) == &item1); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); +} + +void test_next(void) +{ + fr_cursor_t cursor; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item2); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item2); + TEST_CHECK(fr_cursor_current(&cursor) == &item2); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); +} + +void test_next_wrap(void) +{ + fr_cursor_t cursor; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + item_p = fr_cursor_next(&cursor); + TEST_CHECK(!item_p); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + TEST_CHECK(!fr_cursor_current(&cursor)); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(!item_p); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + TEST_CHECK(!fr_cursor_current(&cursor)); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); +} + +void test_cursor_head_tail_null(void) +{ + fr_cursor_t cursor; + test_item_t *head = NULL; + + fr_cursor_init(&cursor, &head); + TEST_CHECK(!fr_cursor_current(&cursor)); + TEST_CHECK(!fr_cursor_head(&cursor)); + TEST_CHECK(!fr_cursor_tail(&cursor)); +} + +void test_cursor_head(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); +} + +void test_cursor_head_after_next(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); +} + +void test_cursor_tail(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item3); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); +} + +void test_cursor_head_after_tail(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_tail(&cursor); + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); +} + +void test_cursor_wrap_after_tail(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_tail(&cursor); + item_p = fr_cursor_next(&cursor); + TEST_CHECK(!item_p); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item3); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(!item_p); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item3); +} + +void test_cursor_append_empty(void) +{ + fr_cursor_t cursor; + test_item_t *item_p; + test_item_t item1 = { "item1", NULL }; + test_item_t *head = NULL; + + fr_cursor_init(&cursor, &head); + fr_cursor_append(&cursor, &item1); + + item_p = fr_cursor_current(&cursor); + TEST_CHECK(item_p == &item1); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == NULL); +} + +void test_cursor_append_empty_3(void) +{ + fr_cursor_t cursor; + test_item_t *item_p; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", NULL }; + test_item_t *head = NULL; + + fr_cursor_init(&cursor, &head); + fr_cursor_append(&cursor, &item1); + fr_cursor_append(&cursor, &item2); + fr_cursor_append(&cursor, &item3); + + item_p = fr_cursor_current(&cursor); + TEST_CHECK(item_p == &item1); + TEST_CHECK(fr_cursor_next(&cursor) == &item2); + TEST_CHECK(fr_cursor_tail(&cursor) == &item3); +} + +void test_cursor_prepend_empty(void) +{ + fr_cursor_t cursor; + test_item_t *item_p; + test_item_t item1 = { "item1", NULL }; + test_item_t *head = NULL; + + fr_cursor_init(&cursor, &head); + fr_cursor_prepend(&cursor, &item1); + + item_p = fr_cursor_current(&cursor); + TEST_CHECK(item_p == &item1); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == NULL); +} + +void test_cursor_insert_into_empty(void) +{ + fr_cursor_t cursor; + test_item_t *item_p; + test_item_t item1 = { "item1", NULL }; + test_item_t *head = NULL; + + fr_cursor_init(&cursor, &head); + fr_cursor_insert(&cursor, &item1); + + item_p = fr_cursor_current(&cursor); + TEST_CHECK(item_p == &item1); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == NULL); +} + +void test_cursor_insert_into_empty_3(void) +{ + fr_cursor_t cursor; + test_item_t *item_p; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", NULL }; + test_item_t *head = NULL; + + fr_cursor_init(&cursor, &head); + fr_cursor_insert(&cursor, &item1); + fr_cursor_insert(&cursor, &item2); + fr_cursor_insert(&cursor, &item3); + + item_p = fr_cursor_current(&cursor); + TEST_CHECK(item_p == &item1); + TEST_CHECK(fr_cursor_next(&cursor) == &item3); + TEST_CHECK(fr_cursor_tail(&cursor) == &item2); +} + +void test_cursor_replace_in_empty(void) +{ + fr_cursor_t cursor; + test_item_t *item_p; + test_item_t item1 = { "item1", NULL }; + test_item_t *head = NULL; + + fr_cursor_init(&cursor, &head); + TEST_CHECK(!fr_cursor_replace(&cursor, &item1)); + + item_p = fr_cursor_current(&cursor); + TEST_CHECK(item_p == &item1); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == NULL); +} + +void test_cursor_prepend_1i_start(void) +{ + fr_cursor_t cursor; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", NULL }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_prepend(&cursor, &item2); + + TEST_CHECK(fr_cursor_current(&cursor) == &item1); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); /* Inserted before item 1 */ + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(!item_p); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item2); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item1); +} + +void test_cursor_append_1i_start(void) +{ + fr_cursor_t cursor; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", NULL }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_append(&cursor, &item2); + + TEST_CHECK(fr_cursor_current(&cursor) == &item1); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item2); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item2); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item2); +} + +void test_cursor_insert_1i_start(void) +{ + fr_cursor_t cursor; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", NULL }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_insert(&cursor, &item2); + + TEST_CHECK(fr_cursor_current(&cursor) == &item1); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item2); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item2); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item2); +} + +void test_cursor_replace_1i_start(void) +{ + fr_cursor_t cursor; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", NULL }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + item_p = fr_cursor_replace(&cursor, &item2); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_current(&cursor); + TEST_CHECK(item_p == &item2); + + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item2); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item2); +} + +void test_cursor_prepend_2i_start(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_prepend(&cursor, &item3); + + TEST_CHECK(fr_cursor_current(&cursor) == &item1); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item2); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item3); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item2); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(!item_p); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item3); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item2); +} + +void test_cursor_append_2i_start(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_append(&cursor, &item3); + + TEST_CHECK(fr_cursor_current(&cursor) == &item1); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item2); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item2); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item3); +} + +void test_cursor_insert_2i_start(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_insert(&cursor, &item3); + + /* + * Order should be + * + * item1 - HEAD + * item3 + * item2 - TAIL + */ + TEST_CHECK(fr_cursor_current(&cursor) == &item1); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item3); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item3); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item2); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item3); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item2); +} + +void test_cursor_replace_2i_start(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + /* + * Order should be + * + * item3 - HEAD + * item2 - TAIL + */ + fr_cursor_init(&cursor, &head); + item_p = fr_cursor_replace(&cursor, &item3); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_current(&cursor); + TEST_CHECK(item_p == &item3); + + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item3); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item2); +} + +void test_cursor_prepend_3i_mid(void) +{ + fr_cursor_t cursor; + test_item_t item4 = { "item4", NULL }; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + fr_cursor_prepend(&cursor, &item4); + + TEST_CHECK(fr_cursor_current(&cursor) == &item2); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item3); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item3); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(!item_p); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item3); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item4); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item3); +} + +void test_cursor_append_3i_mid(void) +{ + fr_cursor_t cursor; + test_item_t item4 = { "item4", NULL }; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + fr_cursor_append(&cursor, &item4); + + TEST_CHECK(fr_cursor_current(&cursor) == &item2); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item3); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item3); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item4); +} + +void test_cursor_insert_3i_mid(void) +{ + fr_cursor_t cursor; + test_item_t item4 = { "item4", NULL }; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + fr_cursor_insert(&cursor, &item4); + + TEST_CHECK(fr_cursor_current(&cursor) == &item2); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item4); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item4); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item3); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item4); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item3); +} + +void test_cursor_replace_3i_mid(void) +{ + fr_cursor_t cursor; + test_item_t item4 = { "item4", NULL }; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + item_p = fr_cursor_replace(&cursor, &item4); + TEST_CHECK(item_p == &item2); + + item_p = fr_cursor_current(&cursor); + TEST_CHECK(item_p == &item4); + + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item3); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item3); +} + +void test_cursor_prepend_3i_end(void) +{ + fr_cursor_t cursor; + test_item_t item4 = { "item4", NULL }; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + fr_cursor_next(&cursor); + fr_cursor_prepend(&cursor, &item4); + + TEST_CHECK(fr_cursor_current(&cursor) == &item3); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(!item_p); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item3); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item4); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item3); +} + +void test_cursor_append_3i_end(void) +{ + fr_cursor_t cursor; + test_item_t item4 = { "item4", NULL }; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + fr_cursor_next(&cursor); + fr_cursor_append(&cursor, &item4); + + TEST_CHECK(fr_cursor_current(&cursor) == &item3); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item4); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item4); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item3); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item4); +} + +void test_cursor_insert_3i_end(void) +{ + fr_cursor_t cursor; + test_item_t item4 = { "item4", NULL }; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + fr_cursor_next(&cursor); + fr_cursor_insert(&cursor, &item4); + + TEST_CHECK(fr_cursor_current(&cursor) == &item3); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item4); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(item_p == &item4); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item3); + + item_p = fr_cursor_next(&cursor); + TEST_CHECK(!item_p); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item4); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item4); +} + +void test_cursor_replace_3i_end(void) +{ + fr_cursor_t cursor; + test_item_t item4 = { "item4", NULL }; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + fr_cursor_next(&cursor); + item_p = fr_cursor_replace(&cursor, &item4); + TEST_CHECK(item_p == &item3); + + item_p = fr_cursor_current(&cursor); + TEST_CHECK(item_p == &item4); + + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); + + item_p = fr_cursor_head(&cursor); + TEST_CHECK(item_p == &item1); + + item_p = fr_cursor_tail(&cursor); + TEST_CHECK(item_p == &item4); +} + +void test_cursor_remove_empty(void) +{ + fr_cursor_t cursor; + test_item_t *item_p; + test_item_t *head = NULL; + + item_p = _fr_cursor_init(&cursor, (void **)&head, offsetof(test_item_t, next), test_iter, &cursor, NULL); + TEST_CHECK(!fr_cursor_remove(&cursor)); +} + +void test_cursor_remove_1i(void) +{ + fr_cursor_t cursor; + test_item_t item1 = { "item1", NULL }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + + item_p = fr_cursor_remove(&cursor); + TEST_CHECK(item_p == &item1); + + TEST_CHECK(!fr_cursor_current(&cursor)); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + TEST_CHECK(!fr_cursor_next(&cursor)); + TEST_CHECK(!fr_cursor_tail(&cursor)); + TEST_CHECK(!fr_cursor_head(&cursor)); +} + +void test_cursor_remove_2i(void) +{ + fr_cursor_t cursor; + test_item_t item2 = { "item2", NULL }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + item_p = fr_cursor_remove(&cursor); + + TEST_CHECK(item_p == &item1); + TEST_CHECK(fr_cursor_current(&cursor) == &item2); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + TEST_CHECK(!fr_cursor_next(&cursor)); + TEST_CHECK(fr_cursor_tail(&cursor) == &item2); + TEST_CHECK(fr_cursor_head(&cursor) == &item2); + + item_p = fr_cursor_remove(&cursor); + TEST_CHECK(item_p == &item2); + + TEST_CHECK(!fr_cursor_current(&cursor)); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + TEST_CHECK(!fr_cursor_next(&cursor)); + TEST_CHECK(!fr_cursor_tail(&cursor)); + TEST_CHECK(!fr_cursor_head(&cursor)); +} + +void test_cursor_remove_3i_start(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + item_p = fr_cursor_remove(&cursor); + TEST_CHECK(item_p == &item1); + TEST_CHECK(fr_cursor_current(&cursor) == &item2); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + TEST_CHECK(fr_cursor_next_peek(&cursor) == &item3); + + item_p = fr_cursor_remove(&cursor); + TEST_CHECK(item_p == &item2); + TEST_CHECK(fr_cursor_current(&cursor) == &item3); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor)); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); + + item_p = fr_cursor_remove(&cursor); + TEST_CHECK(item_p == &item3); + + TEST_CHECK(!fr_cursor_tail(&cursor)); + TEST_CHECK(!fr_cursor_head(&cursor)); +} + +void test_cursor_remove_3i_mid(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_next(&cursor); + + item_p = fr_cursor_remove(&cursor); + TEST_CHECK(item_p == &item2); + TEST_CHECK(fr_cursor_current(&cursor) == &item3); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); + + item_p = fr_cursor_remove(&cursor); + TEST_CHECK(item_p == &item3); + + /* + * We just removed the end of the list + * so current is now NULL. + * + * We don't implicitly start moving backwards. + */ + TEST_CHECK(!fr_cursor_current(&cursor)); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item1); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); + + item_p = fr_cursor_remove(&cursor); + TEST_CHECK(!item_p); + + TEST_CHECK(fr_cursor_tail(&cursor) == &item1); + TEST_CHECK(fr_cursor_head(&cursor) == &item1); +} + +void test_cursor_remove_3i_end(void) +{ + fr_cursor_t cursor; + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + test_item_t *item_p; + test_item_t *head = &item1; + + fr_cursor_init(&cursor, &head); + fr_cursor_tail(&cursor); + + item_p = fr_cursor_remove(&cursor); + TEST_CHECK(item_p == &item3); + TEST_CHECK(!fr_cursor_current(&cursor)); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); + + item_p = fr_cursor_remove(&cursor); + TEST_CHECK(!item_p); + + TEST_CHECK(!fr_cursor_current(&cursor)); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == &item2); + TEST_CHECK(!fr_cursor_next_peek(&cursor)); +} + +void test_cursor_merge_start(void) +{ + fr_cursor_t cursor_a, cursor_b; + + test_item_t item3b = { "item3b", NULL }; + test_item_t item2b = { "item2b", &item3b }; + test_item_t item1b = { "item1b", &item2b }; + + test_item_t item3a = { "item3a", NULL }; + test_item_t item2a = { "item2a", &item3a }; + test_item_t item1a = { "item1a", &item2a }; + + test_item_t *head_a = &item1a; + test_item_t *head_b = &item1b; + + fr_cursor_init(&cursor_a, &head_a); + fr_cursor_init(&cursor_b, &head_b); + fr_cursor_merge(&cursor_a, &cursor_b); + + TEST_CHECK(fr_cursor_current(&cursor_a) == &item1a); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item2a); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item3a); + + TEST_CHECK(fr_cursor_next(&cursor_a) == &item1b); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item2b); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item3b); + TEST_CHECK(!fr_cursor_next(&cursor_a)); + + TEST_CHECK(!fr_cursor_current(&cursor_b)); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor_b)); + TEST_CHECK(!fr_cursor_list_next_peek(&cursor_b)); +} + +void test_cursor_merge_mid(void) +{ + fr_cursor_t cursor_a, cursor_b; + + test_item_t item3b = { "item3b", NULL }; + test_item_t item2b = { "item2b", &item3b }; + test_item_t item1b = { "item1b", &item2b }; + + test_item_t item3a = { "item3a", NULL }; + test_item_t item2a = { "item2a", &item3a }; + test_item_t item1a = { "item1a", &item2a }; + + test_item_t *head_a = &item1a; + test_item_t *head_b = &item1b; + + fr_cursor_init(&cursor_a, &head_a); + fr_cursor_init(&cursor_b, &head_b); + fr_cursor_next(&cursor_b); + fr_cursor_merge(&cursor_a, &cursor_b); + + TEST_CHECK(fr_cursor_current(&cursor_a) == &item1a); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item2a); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item3a); + + TEST_CHECK(fr_cursor_next(&cursor_a) == &item2b); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item3b); + TEST_CHECK(!fr_cursor_next(&cursor_a)); + + TEST_CHECK(!fr_cursor_current(&cursor_b)); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor_b) == &item1b); + TEST_CHECK(!fr_cursor_list_next_peek(&cursor_b)); +} + +void test_cursor_merge_end(void) +{ + fr_cursor_t cursor_a, cursor_b; + + test_item_t item3b = { "item3b", NULL }; + test_item_t item2b = { "item2b", &item3b }; + test_item_t item1b = { "item1b", &item2b }; + + test_item_t item3a = { "item3a", NULL }; + test_item_t item2a = { "item2a", &item3a }; + test_item_t item1a = { "item1a", &item2a }; + + test_item_t *head_a = &item1a; + test_item_t *head_b = &item1b; + + fr_cursor_init(&cursor_a, &head_a); + fr_cursor_init(&cursor_b, &head_b); + fr_cursor_next(&cursor_b); + fr_cursor_next(&cursor_b); + fr_cursor_merge(&cursor_a, &cursor_b); + + TEST_CHECK(fr_cursor_current(&cursor_a) == &item1a); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item2a); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item3a); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item3b); + TEST_CHECK(!fr_cursor_next(&cursor_a)); + + TEST_CHECK(!fr_cursor_current(&cursor_b)); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor_b) == &item2b); + TEST_CHECK(fr_cursor_head(&cursor_b) == &item1b); +} + +void test_cursor_merge_with_empty(void) +{ + fr_cursor_t cursor_a, cursor_b; + + test_item_t item3b = { "item3b", NULL }; + test_item_t item2b = { "item2b", &item3b }; + test_item_t item1b = { "item1b", &item2b }; + + test_item_t *head_a = NULL; + test_item_t *head_b = &item1b; + + fr_cursor_init(&cursor_a, &head_a); + fr_cursor_init(&cursor_b, &head_b); + fr_cursor_merge(&cursor_a, &cursor_b); + + TEST_CHECK(fr_cursor_head(&cursor_a) == &item1b); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item2b); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item3b); + + TEST_CHECK(!fr_cursor_current(&cursor_b)); + TEST_CHECK(!fr_cursor_list_prev_peek(&cursor_b)); + TEST_CHECK(!fr_cursor_list_next_peek(&cursor_b)); +} + +void test_cursor_merge_empty(void) +{ + fr_cursor_t cursor_a, cursor_b; + + test_item_t item3a = { "item3a", NULL }; + test_item_t item2a = { "item2a", &item3a }; + test_item_t item1a = { "item1a", &item2a }; + + test_item_t *head_a = &item1a; + test_item_t *head_b = NULL; + + fr_cursor_init(&cursor_a, &head_a); + fr_cursor_init(&cursor_b, &head_b); + fr_cursor_merge(&cursor_a, &cursor_b); + + TEST_CHECK(fr_cursor_head(&cursor_a) == &item1a); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item2a); + TEST_CHECK(fr_cursor_next(&cursor_a) == &item3a); +} + +void test_cursor_copy(void) +{ + fr_cursor_t cursor_a, cursor_b; + + test_item_t item3 = { "item3", NULL }; + test_item_t item2 = { "item2", &item3 }; + test_item_t item1 = { "item1", &item2 }; + + test_item_t *head = &item1; + + fr_cursor_init(&cursor_a, &head); + fr_cursor_copy(&cursor_b, &cursor_a); + + TEST_CHECK(fr_cursor_head(&cursor_b) == &item1); + TEST_CHECK(fr_cursor_next(&cursor_b) == &item2); + TEST_CHECK(fr_cursor_next(&cursor_b) == &item3); +} + +void test_cursor_free(void) +{ + test_item_t *item1, *item2, *item3; + test_item_t *head = NULL; + fr_cursor_t cursor; + void *item_p; + + item1 = talloc_zero(NULL, test_item_t); + item2 = talloc_zero(NULL, test_item_t); + item3 = talloc_zero(NULL, test_item_t); + + fr_cursor_init(&cursor, &head); + fr_cursor_append(&cursor, item1); + fr_cursor_append(&cursor, item2); + fr_cursor_append(&cursor, item3); + + fr_cursor_next(&cursor); + fr_cursor_list_free(&cursor); + + TEST_CHECK(fr_cursor_current(&cursor) == NULL); + TEST_CHECK(fr_cursor_list_prev_peek(&cursor) == item1); + TEST_CHECK(fr_cursor_tail(&cursor) == item1); + TEST_CHECK(fr_cursor_head(&cursor) == item1); + + item_p = fr_cursor_remove(&cursor); + talloc_free(item_p); +} + +TEST_LIST = { + /* + * Initialisation + */ + { "init_null", test_init_null_item }, + { "init_one", test_init_1i_start }, + { "init_two", test_init_2i_start }, + + /* + * Normal iteration + */ + { "next", test_next }, + { "next_wrap", test_next_wrap }, /* should not wrap */ + + /* + * Jump to head/tail + */ + { "head_tail_null", test_cursor_head_tail_null }, + { "head", test_cursor_head }, + { "head_after_next", test_cursor_head_after_next }, + { "tail", test_cursor_tail }, + { "head_after_tail", test_cursor_head_after_tail }, + { "wrap_after_tail", test_cursor_wrap_after_tail }, + + /* + * Insert with empty list + */ + { "prepend_empty", test_cursor_prepend_empty }, + { "append_empty", test_cursor_append_empty }, + { "append_empty_3", test_cursor_append_empty_3 }, + { "insert_into_empty", test_cursor_insert_into_empty }, + { "insert_into_empty_3", test_cursor_insert_into_empty_3 }, + { "replace_in_empty", test_cursor_replace_in_empty }, + + /* + * Insert with one item list + */ + { "prepend_1i_start", test_cursor_prepend_1i_start}, + { "append_1i_start", test_cursor_append_1i_start }, + { "insert_1i_start", test_cursor_insert_1i_start }, + { "replace_1i_start", test_cursor_replace_1i_start }, + + /* + * Insert with two item list + */ + { "prepend_2i_start", test_cursor_prepend_2i_start }, + { "append_2i_start", test_cursor_append_2i_start }, + { "insert_2i_start", test_cursor_insert_2i_start }, + { "replace_2i_start", test_cursor_replace_2i_start }, + + /* + * Insert with three item list (with cursor on item2) + */ + { "prepend_3i_mid", test_cursor_prepend_3i_mid }, + { "append_3i_mid", test_cursor_append_3i_mid }, + { "insert_3i_mid", test_cursor_insert_3i_mid }, + { "replace_3i_mid", test_cursor_replace_3i_mid }, + + /* + * Insert with three item list (with cursor on item3) + */ + { "prepend_3i_end", test_cursor_prepend_3i_end }, + { "append_3i_end", test_cursor_append_3i_end }, + { "insert_3i_end", test_cursor_insert_3i_end }, + { "replace_3i_end", test_cursor_replace_3i_end }, + + /* + * Remove + */ + { "remove_empty", test_cursor_remove_empty }, + { "remove_1i", test_cursor_remove_1i }, + { "remove_2i", test_cursor_remove_2i }, + { "remove_3i_start", test_cursor_remove_3i_start }, + { "remove_3i_mid", test_cursor_remove_3i_mid }, + { "remove_3i_end", test_cursor_remove_3i_end }, + + /* + * Merge + */ + { "merge_start", test_cursor_merge_start }, + { "merge_mid", test_cursor_merge_mid }, + { "merge_end", test_cursor_merge_end }, + { "merge_with_empty", test_cursor_merge_with_empty }, + { "merge_empty", test_cursor_merge_empty }, + + /* + * Copy + */ + { "copy", test_cursor_copy }, + + /* + * Free + */ + { "free", test_cursor_free }, + { 0 } +}; +#endif