From f30f7a64496dea1af306fef9327c98d27190d1dd Mon Sep 17 00:00:00 2001 From: Phireh Date: Mon, 6 Jan 2025 19:39:32 +0100 Subject: [PATCH] Initial commit --- .gitignore | 2 + Makefile | 8 + ihct/ihct.c | 440 ++++++++++++++++++++++++++++++++++++++++++++++++++ ihct/ihct.h | 151 +++++++++++++++++ ihct/vector.c | 41 +++++ ihct/vector.h | 24 +++ list.c | 77 +++++++++ list.h | 261 ++++++++++++++++++++++++++++++ tests.c | 90 +++++++++++ tree.h | 32 ++++ 10 files changed, 1126 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 ihct/ihct.c create mode 100644 ihct/ihct.h create mode 100644 ihct/vector.c create mode 100644 ihct/vector.h create mode 100644 list.c create mode 100644 list.h create mode 100644 tests.c create mode 100644 tree.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c245e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +libihct.so +tests diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3ff2d8e --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +IHCT_CFLAGS=-Wall -Wextra -Wpedantic -Wfatal-errors -std=gnu99 -fPIC -shared +TEST_CFLAGS=-Wall -Wextra -Wpedantic -Wfatal-errors -DIHCT_SHORT -L./ -Wl,-rpath ./ + +tests: tests.c list.h libihct.so + gcc $(TEST_CFLAGS) tests.c -lihct -o tests + +libihct.so: ihct/vector.c ihct/ihct.c + gcc $(IHCT_CFLAGS) $^ -o libihct.so diff --git a/ihct/ihct.c b/ihct/ihct.c new file mode 100644 index 0000000..2258ed5 --- /dev/null +++ b/ihct/ihct.c @@ -0,0 +1,440 @@ +#include "ihct.h" +#include "vector.h" + +#include +#include +#include +#include +#include +#include // handle tests that yield fatal signals. +#include +#include +#include + +// The point of which to restore to on a fatal signal. +jmp_buf restore_environment; +// the sigaction used for catching fatal signals. +struct sigaction recover_action; +// The final summary string printed. +char *summary_str; +// A list of all units. +static ihct_vector *testunits; +// An array of all first failed (or last if all successful) assert results in every test. +static ihct_test_result **ihct_results; + +// Object representing a testing unit, containing the units name and its procedure +// (implemented test function). +typedef struct { + char *name; + ihct_test_proc procedure; +} ihct_unit; + + +// The number of seconds passed until a test is considered timedout. +// Default 3. Can be set with -t [time in sec] +int test_timeout = 3; + +pthread_cond_t routine_done = PTHREAD_COND_INITIALIZER; +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +// These are ISO/IEC 6429 escape sequences for +// communicating text attributes to terminal emulators. +// Note that some compilers do not understand '\x1b', and therefore \033[0m is +// used instead. +#define IHCT_RESET "\033[0m" +#define IHCT_BOLD "\033[1m" +#define IHCT_FG_GRAY "\033[30;1m" +#define IHCT_FG_RED "\033[31;1m" +#define IHCT_FG_GREEN "\033[32;1m" +#define IHCT_FG_YELLOW "\033[33;1m" +#define IHCT_FG_BLUE "\033[34;1m" +#define IHCT_FG_MAGENTA "\033[35;1m" +#define IHCT_FG_CYAN "\033[36;1m" +#define IHCT_FG_WHITE "\033[37;1m" +#define IHCT_BG_BLACK "\033[40;1m" +#define IHCT_BG_RED "\033[41;1m" +#define IHCT_BG_GREEN "\033[42;1m" +#define IHCT_BG_YELLOW "\033[43;1m" +#define IHCT_BG_BLUE "\033[44;1m" +#define IHCT_BG_MAGENTA "\033[45;1m" +#define IHCT_BG_CYAN "\033[46;1m" +#define IHCT_BG_GRAY "\033[47;1m" + +// Procedure called when a signal is thrown within a test. When a fatal signal is +// recieved, we jump back to before the test is ran, giving the signal code. +static void ihct_recovery_proc(int sig) { + // Restore. + longjmp(restore_environment, sig); +} + +// Binds the sigaction to signals, and make it call ihct_recovery_proc. +static void ihct_setup_recover_action(void) { + recover_action.sa_handler = &ihct_recovery_proc; + sigemptyset(&recover_action.sa_mask); + recover_action.sa_flags = 0; + // binding sigactions. Dont pick up on user interrupts (let them be managed + // normally). + sigaction(SIGSEGV, &recover_action, NULL); + sigaction(SIGTERM, &recover_action, NULL); + sigaction(SIGFPE, &recover_action, NULL); + sigaction(SIGILL, &recover_action, NULL); + sigaction(SIGABRT, &recover_action, NULL); + sigaction(SIGBUS, &recover_action, NULL); +} + + +void ihct_print_result(ihct_test_result *result) { + switch (result->status) { + case PASS: fputs(IHCT_BG_GREEN IHCT_BOLD "." IHCT_RESET, stdout); break; + case FAIL_FORCE: + case FAIL: fputs(IHCT_BG_RED IHCT_BOLD ":" IHCT_RESET, stdout); break; + case ERR: fputs(IHCT_BG_RED IHCT_BOLD "!" IHCT_RESET, stdout); break; + case TIMEOUT: fputs(IHCT_BG_YELLOW IHCT_BOLD "?" IHCT_RESET, stdout); break; + } +} +// Reallocates and appends string s to summary_str +void ihct_add_to_summary(char *s) { + char *p = realloc(summary_str, strlen(summary_str) + strlen(s) + 1); + if(!p) { + printf("Couldn't allocate a str big enough to hold summary. Aborting.\n"); + return; + } + summary_str = p; + strcat(summary_str, s); +} +void ihct_add_error_to_summary(ihct_test_result *res, ihct_unit *unit) { + char *msg; + char *msg_format; + size_t msg_size; + switch (res->status) { + case PASS: break; + case FAIL: + msg_format = IHCT_BOLD "%s:%d: " + IHCT_RESET "assertion in '" + IHCT_BOLD "%s" + IHCT_RESET "' " + IHCT_FG_RED "failed" + IHCT_RESET ":\n\t'" + IHCT_FG_YELLOW "%s" + IHCT_RESET "'\n"; + msg_size = snprintf(NULL, 0, msg_format, res->file, res->line, + unit->name, res->code) + 1; + msg = calloc(msg_size, sizeof(*msg)); + sprintf(msg, msg_format, res->file, res->line, unit->name, res->code); + break; + case FAIL_FORCE: + msg_format = IHCT_BOLD "%s:%d: " + IHCT_RESET "'" + IHCT_BOLD "%s" + IHCT_RESET "' " + IHCT_FG_RED "forcefully failed" + IHCT_RESET ".\n"; + msg_size = snprintf(NULL, 0, msg_format, res->file, res->line, + unit->name) + 1; + msg = calloc(msg_size, sizeof(*msg)); + sprintf(msg, msg_format, res->file, res->line, unit->name); + break; + case ERR: + msg_format = "unit '" + IHCT_BOLD "%s" + IHCT_RESET "' had to restore because of fatal signal (" + IHCT_FG_RED "%s" + IHCT_RESET ")\n"; + msg_size = snprintf(NULL, 0, msg_format, unit->name, res->code) + 1; + msg = calloc(msg_size, sizeof(*msg)); + sprintf(msg, msg_format, unit->name, res->code); + break; + case TIMEOUT: + msg_format = "unit '" + IHCT_BOLD "%s" + IHCT_RESET "' " + IHCT_FG_YELLOW "timed out " + IHCT_RESET "(took " + IHCT_FG_YELLOW "5 " + IHCT_RESET "seconds).\n"; + msg_size = snprintf(NULL, 0, msg_format, unit->name) + 1; + msg = calloc(msg_size, sizeof(*msg)); + sprintf(msg, msg_format, unit->name); + } + ihct_add_to_summary(msg); + + free(msg); +} + +bool ihct_assert_impl(bool eval, ihct_test_result *result, char *code, char *file, + unsigned long line) { + result->status = eval ? PASS : FAIL; + + if(!eval) { + result->file = file; + result->line = line; + result->code = code; + return false; + } + return true; +} + +void ihct_pass_impl(ihct_test_result *result, char *file, unsigned long line) { + result->status = PASS; + result->file = file; + result->line = line; +} + +void ihct_fail_impl(ihct_test_result *result, char *file, unsigned long line) { + result->status = FAIL_FORCE; + result->file = file; + result->line = line; +} + +static ihct_unit *ihct_init_unit(char *name, ihct_test_proc procedure) { + ihct_unit *unit = (ihct_unit *)malloc(sizeof(ihct_unit)); + char *strmem = malloc(strlen(name) + 1); + strcpy(strmem, name); + unit->name = strmem; + unit->procedure = procedure; + + return unit; +} + +void ihct_construct_test_impl(char *name, ihct_test_proc procedure) { + ihct_unit *unit = ihct_init_unit(name, procedure); + ihct_vector_add(testunits, unit); +} + +static void ihct_unit_free(ihct_unit *unit) { + free(unit->name); + free(unit); +} + +void ihct_init(void) { + // atm, only initializes the unit list. Is this neccessary? + testunits = ihct_vector_init(); +} + +struct routine_run_unit_data { + ihct_test_proc proc; + ihct_test_result *result; +}; + +// Routine run in a separate thread to execute the unit. +void *routine_run_unit(void *arg) { + struct routine_run_unit_data *data = (struct routine_run_unit_data *)arg; + + int old; + // Allow the thread to be canceled. + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old); + + + // Create a jump point, to be able to restore when encountering fatal signal. + // When returning here because of a fatal signal, we abort unit with status + // ERR. Has to be done inside of thread, refer to man setjmp: + // "If, in a multithreaded program, a longjmp() call employs an env + // buffer that was initialized by a call to setjmp() in a different + // thread, the behavior is undefined.". + ihct_setup_recover_action(); + int restore_status = setjmp(restore_environment); + if(restore_status != 0) { + char *p = malloc(strlen(strsignal(restore_status)) + 1); + strcpy(p, strsignal(restore_status)); + data->result->code = p; + data->result->status = ERR; + + // We still want to emit finished signal + pthread_mutex_lock(&lock); + pthread_cond_signal(&routine_done); + pthread_mutex_unlock(&lock); + return NULL; + } + + + // Run test, and save it's result. + (*data->proc)(data->result); + + // Emit signal that thread is finished. + pthread_mutex_lock(&lock); + pthread_cond_signal(&routine_done); + pthread_mutex_unlock(&lock); + return NULL; +} + +static ihct_test_result *ihct_run_specific(ihct_unit *unit) { + // Allocate memory for the tests result, and set it to passed by default. + ihct_test_result *result = malloc(sizeof(ihct_test_result)); + result->status = PASS; + + // lock current thread. + pthread_mutex_lock(&lock); + + // Create a separate thread to run the test in. We set a limited time + // the process may be run, and abort if it times out. + struct timespec timeout; + clock_gettime(CLOCK_REALTIME, &timeout); + timeout.tv_sec += test_timeout; + + // Create a temporary data struct to carry data into thread. + struct routine_run_unit_data data = {unit->procedure, result}; + + // Create new thread to run the unit routine. + pthread_t tid; + pthread_cond_init(&routine_done, NULL); + pthread_create(&tid, NULL, routine_run_unit, &data); + + int err = pthread_cond_timedwait(&routine_done, &lock, &timeout); + + pthread_mutex_unlock(&lock); + + // If timed out, force quit thread and return TIMEOUT. + // Note that this is not safe memory. There is a high chance that + // the thread may not be freed. Looking into solving this. + if(err == ETIMEDOUT) { + pthread_cancel(tid); + + result->status = TIMEOUT; + return result; + } + + //pthread_join(procedure, NULL); + pthread_join(tid, NULL); + + // Run test, and save it's result into i. + //(*unit->procedure)(result); + + return result; +} + +int ihct_run(int argc, char **argv) { + unsigned unit_count = testunits->size; + // Allocate results + ihct_results = calloc(unit_count, sizeof(ihct_test_result *)); + + unsigned failed_count = 0; + + // initialize the summary string + summary_str = malloc(sizeof(char)); + *summary_str = '\0'; + + // handle args + int c; + while((c = getopt(argc, argv, "t:")) != -1) { + switch(c) { + case 't': + test_timeout = atoi(optarg); + break; + case '?': + printf("unknown option '%c'.\n", optopt); + } + } + + // start clock + struct timespec tbegin, tend; + clock_gettime(CLOCK_MONOTONIC, &tbegin); + + // Iterate over every test + for(unsigned i = 0; i < unit_count; i++) { + ihct_unit *unit = ihct_vector_get(testunits, i); + + ihct_results[i] = ihct_run_specific(unit); + + // ensure 80 width + if(i % 80 == 0 && i != 0) putc('\n', stdout); + + ihct_print_result(ihct_results[i]); + fflush(stdout); + + if(ihct_results[i]->status) { + failed_count++; + ihct_add_error_to_summary(ihct_results[i], unit); + } + // Frees both the unit and the result. + ihct_unit_free(unit); + + // Also frees dynamic allocated string if status is err (the signal name). + if(ihct_results[i]->status == ERR) free(ihct_results[i]->code); + + free(ihct_results[i]); + } + free(ihct_results); + ihct_vector_free(testunits); + + clock_gettime(CLOCK_MONOTONIC, &tend); + double elapsed = (tend.tv_sec - tbegin.tv_sec); + elapsed += (tend.tv_nsec - tbegin.tv_nsec) / 1000000000.0; + + // print all messages + if(strlen(summary_str) > 4) printf("\n\n%s\n", summary_str); + else puts("\n\n"); + + // Free summary str + free(summary_str); + + printf("tests took %.2f seconds\n", elapsed); + if(failed_count) { + char *status_format = IHCT_FG_GREEN "%d successful " + IHCT_RESET "and " + IHCT_FG_RED "%d failed " + IHCT_RESET "of " + IHCT_FG_YELLOW "%d run" + IHCT_RESET "\n"; + printf(status_format, unit_count-failed_count, failed_count, + unit_count); + + printf(IHCT_FG_RED "FAILURE\n" IHCT_RESET); + return 1; + } + + char *status_format = IHCT_FG_GREEN "%d successful " + IHCT_RESET "of " + IHCT_FG_YELLOW "%d run" + IHCT_RESET "\n"; + printf(status_format, unit_count, unit_count); + + printf(IHCT_FG_GREEN "SUCCESS\n" IHCT_RESET); + return 0; +} + +// Lets the program run tests on itself. This is done with the compiler flag +// IHCT_TEST_SELF. Still requires an external main entrypoint. +#ifdef IHCT_TEST_SELF + +// Create two internal test procedures, for testing the test creation. +static void itest_true(ihct_test_result *result) { + IHCT_ASSERT(true); +} +static void itest_false(ihct_test_result *result) { + IHCT_ASSERT(false); +} + +IHCT_TEST(self_unit_create) { + ihct_unit *u = ihct_init_unit("internal_true", &itest_true); + IHCT_ASSERT_STR(u->name, "internal_true"); + IHCT_ASSERT(&itest_true == u->procedure); +} + +IHCT_TEST(self_vector_create) { + ihct_vector *v = ihct_init_vector(); + + IHCT_ASSERT(v != NULL); + IHCT_ASSERT(v->data == NULL); + IHCT_ASSERT(v->size == 0); + + ihct_free_vector(v); +} + +IHCT_TEST(self_vector_all) { + ihct_vector *v = ihct_init_vector(); + + for(int i = 0; i < 1000; ++i) { + int *t = malloc(sizeof(int)); + *t = i * 2; + ihct_vector_add(v, t); + } + + for(int i = 0; i < 1000; ++i) { + int *t = ihct_vector_get(v, i); + IHCT_ASSERT(i * 2 == *t); + free(t); + } + + ihct_free_vector(v); +} +#endif \ No newline at end of file diff --git a/ihct/ihct.h b/ihct/ihct.h new file mode 100644 index 0000000..957145e --- /dev/null +++ b/ihct/ihct.h @@ -0,0 +1,151 @@ +#ifndef IHCT_H +#define IHCT_H + +#include +#include +#include + +// Structure for a testunits return value. Contains state, the code (assert) which +// failed the test, and a reference to where the code is. +typedef struct { + enum {PASS, FAIL, FAIL_FORCE, ERR, TIMEOUT} status; + char *code; + char *file; + unsigned long line; +} ihct_test_result; + +// Short for a function returning a test_result pointer, with no arguments. +typedef void (*ihct_test_proc)(ihct_test_result *); + +// Called within a test. +bool ihct_assert_impl(bool eval, ihct_test_result *result, char *code, char *file, + unsigned long line); + +void ihct_pass_impl(ihct_test_result *result, char *file, unsigned long line); +void ihct_fail_impl(ihct_test_result *result, char *file, unsigned long line); + +// Called on test unit construction. +void ihct_construct_test_impl(char *s, ihct_test_proc proc); + +// Runs all tests. +int ihct_run(int argc, char **argv); + +// Initializes the unitlist (Has to be done before all testing units are created). +// Using priority to ensure that the unit list is constructed before it gets populated. +void ihct_init(void) __attribute__((constructor(101))); + +// Assertions +/// @defgroup assertions Assertions +/// @brief Wraps all assertions. +/// +/// Decides the success of a test. Right now, tests can only result in +/// PASS or FAIL. Assertions are only given for expressions. + +/// @brief Asserts a statement inside a test unit. If the expression is false, +/// the unit will fail the test. +/// @ingroup assertions +/// @param expr the expression to evaluate. +/// +/// Can be shortened to remove 'IHCT_' prefix by defining IHCT_SHORT. +#define IHCT_ASSERT(expr) \ + if(!ihct_assert_impl(expr, result, #expr, __FILE__, __LINE__)) return + +/// @brief Asserts a statement inside a test unit. If the expression is true, +/// the unit will fail the test. +/// @ingroup assertions +/// @param expr the expression to evaluate. +/// +/// Can be shortened to remove 'IHCT_' prefix by defining IHCT_SHORT. +#define IHCT_NASSERT(expr) \ + if(!ihct_assert_impl(!expr, result, "!(" #expr ")", __FILE__, __LINE__)) return + +/// @brief Asserts two strings inside a test unit to be equal. If there is any difference +/// in the strings, the unit will fail the test. +/// @ingroup assertions +/// @param s1 first string to compare +/// @param s2 second string to compare +/// +/// Can be shortened to remove 'IHCT_' prefix by defining IHCT_SHORT. +#define IHCT_ASSERT_STR(s1, s2) \ + if(!ihct_assert_impl(!strcmp(s1, s2), result, #s1 " == " #s2, __FILE__, \ + __LINE__)) return +/// @brief Asserts two strings inside a test unit not to be equal. If there is any +/// difference in the strings, the unit will fail the test. +/// @ingroup assertions +/// @param s1 first string to compare +/// @param s2 second string to compare +/// +/// Can be shortened to remove 'IHCT_' prefix by defining IHCT_SHORT. +#define IHCT_NASSERT_STR(s1, s2) \ + if(!ihct_assert_impl(strcmp(s1, s2), result, #s1 " != " #s2, __FILE__, \ + __LINE__)) return + +/// @brief Set the test as passed and return. +/// @ingroup assertions +/// +/// Used for more complex tests where the PASS/FAIL status is more complex +/// than an assert. Can be shortened to remove 'IHCT_' prefix by defining IHCT_SHORT. +#define IHCT_PASS() \ + do { ihct_pass_impl(result, __FILE__, __LINE__); return; } while(0) + +/// @brief Set the test as failed and return. +/// @ingroup assertions +/// +/// Used for more complex tests where the PASS/FAIL status is more complex +/// than an assert. Can be shortened to remove 'IHCT_' prefix by defining IHCT_SHORT. +#define IHCT_FAIL() \ + do { ihct_fail_impl(result, __FILE__, __LINE__); return; } while(0) + +// Function macros +/// @defgroup funcs Testing functions +/// @brief More general macros for function. + +/// @brief Runs all tests. Is to be called once in the main entrypoint. +/// @ingroup funcs +/// @code +/// int main(int argc, char **argv) { +/// return IHCT_RUN(argc, argv); +/// } +/// @endcode +/// @param argc argument count, directly passed from main. +/// @param argv argument array, directly passed from main. +#define IHCT_RUN(argc, argv) \ + ihct_run(argc, argv) + +// Create a new test unit, and adds it using 'ihct_add_test'. +/// @brief Create a new test unit, which can take any number of asserts. +/// @ingroup funcs +/// @code +/// IHCT_TEST(basic_test) { +/// IHCT_ASSERT(1 == 1); +/// } +/// @endcode +/// @param name the name of the test. +/// +/// Can be shortened to remove 'IHCT_' prefix by defining IHCT_SHORT. +#define IHCT_TEST(name) \ + static void test_##name(ihct_test_result *result); \ + static void __attribute__((constructor(102))) __construct_test_##name(void) { \ + ihct_construct_test_impl(#name, &test_##name); \ + } \ + static void test_##name(ihct_test_result *result) + +/// @brief Defines a fixture with data to be preloaded before a test. +/// A ficture is included by a IHCT_REQUIRE inside a test. +#define IHCT_FIXTURE(name) _Static_assert(0, "Fixtures not implemented.") + +/// @brief Make the test require the given fixtures. +/// @param ... one or more fixture names. +#define IHCT_REQUIRE(...) _Static_assert(0, "Fixture requirements not implemented.") + +#ifdef IHCT_SHORT +#define TEST(name) IHCT_TEST(name) +#define ASSERT(expr) IHCT_ASSERT(expr) +#define NASSERT(expr) IHCT_NASSERT(expr) +#define ASSERT_STR(s1, s2) IHCT_ASSERT_STR(s1, s2) +#define NASSERT_STR(s1, s2) IHCT_NASSERT_STR(s1, s2) +#define PASS() IHCT_PASS() +#define FAIL() IHCT_FAIL() +#endif + +#endif \ No newline at end of file diff --git a/ihct/vector.c b/ihct/vector.c new file mode 100644 index 0000000..c304a51 --- /dev/null +++ b/ihct/vector.c @@ -0,0 +1,41 @@ +#include "vector.h" + +#include + +ihct_vector *ihct_vector_init() { + ihct_vector *v = malloc(sizeof(*v)); + if(v == NULL) { + printf("Couldn't allocate memory for vector.\n"); + exit(EXIT_FAILURE); + } + v->size = 0; + v->data = NULL; + return v; +} +void ihct_vector_add(ihct_vector *v, void *obj) { + if(v->size == 0) { + // Allocate a single + v->data = malloc(sizeof(obj)); + if(v->data == NULL) { + printf("Couldn't allocate memory for object.\n"); + exit(EXIT_FAILURE); + } + v->data[0] = obj; + } else { + void *p = realloc(v->data, (v->size + 1) * sizeof(obj)); + if(p == NULL) { + printf("Couldn't allocate memory for object.\n"); + exit(EXIT_FAILURE); + } + v->data = p; + v->data[v->size] = obj; + } + v->size++; +} +void *ihct_vector_get(ihct_vector *v, int index) { + return v->data[index]; +} +void ihct_vector_free(ihct_vector *v) { + free(v->data); + free(v); +} \ No newline at end of file diff --git a/ihct/vector.h b/ihct/vector.h new file mode 100644 index 0000000..e6a1432 --- /dev/null +++ b/ihct/vector.h @@ -0,0 +1,24 @@ +#ifndef IHCT_VECTOR_H +#define IHCT_VECTOR_H + +#include + +// Datatype representing a vector. to be used internally in IHCT_RUN +typedef struct { + void **data; + size_t size; +} ihct_vector; + +// Allocates a new vector with capacity cap. +ihct_vector *ihct_vector_init(); + +// Add a pointer to a allocated object at the end of the vector. +void ihct_vector_add(ihct_vector *v, void *obj); + +// Gets the object at location index in vector v. +void *ihct_vector_get(ihct_vector *v, int index); + +// Deallocates the vector. +void ihct_vector_free(ihct_vector *v); + +#endif \ No newline at end of file diff --git a/list.c b/list.c new file mode 100644 index 0000000..2396c69 --- /dev/null +++ b/list.c @@ -0,0 +1,77 @@ +#include +// Single linked list with a single type +#if 0 +#define MAKE_LIST(type) \ +struct type##_node_t; \ +typedef struct { \ + type data; \ + struct type##_node_t *next; \ +} type##_node_t; \ +typedef struct type##_node_t* type##_node_t_ptr + + +MAKE_LIST(int); +MAKE_LIST(char); +#endif + +// Single linked list with fat struct + +#if 1 +typedef struct node_ node_t; +typedef struct node_* node_ptr; +typedef enum { INT, UINT, CHAR, FLOAT, DOUBLE, CHAR_PTR } node_value_t; + +typedef struct node_ { + int type; + union { + int vi; + unsigned int vii; + char cv; + float fv; + double dv; + void *ptr; + }; + node_ptr* children; + //node_ptr prev; +} node_t; +#endif +// Time (single-linked) Time (double-linked) +// Insertar al principio O(1) O(1) +// Insertar al final O(n) / O(1) O(n) / O(1) +// Insertar arbitrario O(n) O(n/2) / O(1) +// Borrar arbitrario O(n) O(n/2) / O(1) +// Buscar O(n) O(n/2) / O(1) + + +int main() +{ + node_t node1 = { INT, { .vi = -1 }, 0 }; + node_t node2 = { UINT, { .vii = 1 }, 0 }; + node_t node3 = { CHAR, { .cv = 'v' }, 0 }; + node1.next = &node2; + node2.next = &node3; + + node_ptr n = &node1; + do { + switch(n->type) { + case INT: + printf("%d\n", n->vi); + break; + case UINT: + printf("%d\n", n->vii); + break; + case CHAR: + printf("%c\n", n->cv); + break; + default: + break; + } + } while ((n = n->next)); + return 0; +} + +/* int num = (int)this; */ + +/* &(this->mem) */ +/* int *member = num + offsetof(node_t, next); */ +/* *member = 1; */ diff --git a/list.h b/list.h new file mode 100644 index 0000000..677bebc --- /dev/null +++ b/list.h @@ -0,0 +1,261 @@ +#ifndef LIST_H +#define LIST_H +#include +// Single linked list with a single type +#if 0 +#define MAKE_LIST(type) \ +struct type##_node_t; \ +typedef struct { \ + type data; \ + struct type##_node_t *next; \ +} type##_node_t; \ +typedef struct type##_node_t* type##_node_t_ptr + + +MAKE_LIST(int); +MAKE_LIST(char); +#endif + +// Single linked list with fat struct + +#if 1 +typedef struct node_ node_t; +typedef struct node_* node_ptr; +typedef enum { INT, UINT, CHAR, FLOAT, DOUBLE, CHAR_PTR, VOID_PTR } node_value_t; + +typedef struct node_ { + int type; + union { + int vi; + unsigned int vui; + char vc; + float vf; + double vd; + char* vcp; + void *vptr; + }; + node_ptr next; +} node_t; + +void list_append(node_t *start, node_t *tail) +{ + node_t *n = start; + + while (n->next) + n = n->next; + n->next = tail; +} + +int list_size(node_t *start) +{ + node_t *n = start; + int size = 1; + while (n->next) + { + n = n->next; + ++size; + } + return size; +} + +node_t *list_nth(node_t *head, int idx) +{ + int i = 0; + node_t *n = head; + while (i < idx && n->next) + { + ++i; + n = n->next; + } + if (i == idx) + return n; + else + return 0; +} + +// TODO: Free memory option +int list_delete_nth(node_t *head, int idx) +{ + int i = 0; + node_t *n = head; + node_t *p; + while (i < idx && n->next) + { + ++i; + p = n; + n = n->next; + } + if (i == idx) + { + if (n->next) + { + p->next = n->next; + } + else + p->next = 0; + + return 0; + } + else + { + return -1; + } +} + +node_t *list_tail(node_t *head) +{ + node_t *n = head; + while (n->next) + n = n->next; + + return n; +} + +int list_is_circular(node_t* start) +{ + node_t* n = start; + while ((n = n->next) && n != start); + return start->next && n == start; +} + + +#endif + +//Doubly linked list +#if 1 +typedef struct dnode_ dnode_t; +typedef struct dnode_* dnode_ptr; +//typedef enum { INT, UINT, CHAR, FLOAT, DOUBLE, CHAR_PTR, VOID_PTR } dnode_value_t; + +typedef struct dnode_ { + int type; + union { + int vi; + unsigned int vui; + char vc; + float vf; + double vd; + char* vcp; + void *vptr; + }; + dnode_ptr next; + dnode_ptr prev; +} dnode_t; + +void dlist_append(dnode_t *start, dnode_t *tail) +{ + dnode_t *n = start; + + while (n->next) + n = n->next; + n->next = tail; + tail->prev = n; +} + +int dlist_size(dnode_t *node) +{ + dnode_t *forward = node; + dnode_t *backward = node; + int size = 1; + + while ((backward = backward->prev)) ++size; + while ((forward = forward->next)) ++size; + + return size; +} + +dnode_t *dlist_nth(int idx, dnode_t *start, dnode_t *end, unsigned int size) +{ + int i = 0; + dnode_t *current = 0; + int reverse = 0; + if(end && size) { + int half = size / 2; + if(idx > half) { + reverse = 1; + i = size - 1; + } + } + + current = (reverse) ? end : start; + while(i != idx && current) { + current = (reverse) ? current->prev : current->next; + i += reverse ? -1 : +1; + } + + return current; +} + +dnode_t *dlist_nth_circular(int steps, dnode_t* start) +{ + int i = 0; + dnode_t* current = start; + int absteps = steps < 0 ? -steps : steps; + //for(int i = 0; i != absteps && current; i++) + while(i++ != absteps && current) + current = (steps < 0) ? current->prev : current->next; + return current; +} + +int dlist_is_circular(dnode_t* start) +{ + dnode_t* n = start; + while ((n = n->next) && n != start); + return start->next && n == start; +} + +/*node_t *list_nth(node_t *head, int idx) +*{ +* int i = 0; +* node_t *n = head; +* while (i < idx && n->next) +* { +* ++i; +* n = n->next; +* } +* if (i == idx) +* return n; +* else +* return 0; +*} +*/ +// TODO: Free memory option +/*int list_delete_nth(node_t *head, int idx) +*{ +* int i = 0; +* node_t *n = head; +* node_t *p; +* while (i < idx && n->next) +* { +* ++i; +* p = n; +* n = n->next; +* } +* if (i == idx) +* { +* if (n->next) +* { +* p->next = n->next; +* } +* else +* p->next = 0; +* +* return 0; +* } +* else +* { +* return -1; +* } +*} +* +*node_t *list_tail(node_t *head) +*{ +* node_t *n = head; +* while (n->next) +* n = n->next; +* +* return n; +*} +*/ +#endif +#endif //LIST_H diff --git a/tests.c b/tests.c new file mode 100644 index 0000000..0949e34 --- /dev/null +++ b/tests.c @@ -0,0 +1,90 @@ +#include "ihct/ihct.h" +#include "list.h" + +TEST(node_create) +{ + node_t node = { INT, { .vi = -1 }, 0 }; + ASSERT(node.type == INT && node.vi == -1); +} + +TEST(list_create) +{ + node_t head = { INT, { .vi = 1 }, 0 }; + node_t n = { INT, { .vi = 2 }, 0 }; + list_append(&head, &n); + ASSERT(head.next == &n); +} + +TEST(list_query) +{ + node_t n0 = { INT, { .vi = 1 }, 0 }; + node_t n1 = { INT, { .vi = 2 }, 0 }; + node_t n2 = { INT, { .vi = 3 }, 0 }; + list_append(&n0, &n1); + list_append(&n0, &n2); + ASSERT(list_nth(&n0, 1) == &n1); +} + +TEST(list_delete) +{ + node_t n0 = { INT, { .vi = 1 }, 0 }; + node_t n1 = { INT, { .vi = 2 }, 0 }; + node_t n2 = { INT, { .vi = 3 }, 0 }; + list_append(&n0, &n1); + list_append(&n0, &n2); + list_delete_nth(&n0, 1); + ASSERT(list_nth(&n0, 1) == &n2); +} + +TEST(dlist_size) +{ + dnode_t n0 = { INT, { .vi = 1 }, 0, 0 }; + dnode_t n1 = { INT, { .vi = 2 }, 0, 0 }; + dnode_t n2 = { INT, { .vi = 3 }, 0, 0 }; + dlist_append(&n0, &n1); + dlist_append(&n1, &n2); + //list_delete_nth(&n0, 1); + ASSERT(dlist_size(&n0) == 3); + ASSERT(dlist_size(&n0) == 3); +} + +TEST(dlist_nth) +{ + dnode_t n0 = { INT, { .vi = 1 }, 0, 0 }; + dnode_t n1 = { INT, { .vi = 1 }, 0, 0 }; + dnode_t n2 = { INT, { .vi = 1 }, 0, 0 }; + dnode_t n3 = { INT, { .vi = 1 }, 0, 0 }; + dlist_append(&n0, &n1); + dlist_append(&n1, &n2); + dlist_append(&n2, &n3); + ASSERT(dlist_nth(1, &n0, 0, 0) == &n1); + ASSERT(dlist_nth(1, &n0, &n3, 4) == &n1); + ASSERT(dlist_nth(2, &n0, 0, 0) == &n2); + ASSERT(dlist_nth(2, &n0, &n3, 4) == &n2); + ASSERT(dlist_nth(0, &n0, 0, 0) == &n0); + ASSERT(dlist_nth(3, &n0, &n3, 4) == &n3); + ASSERT(!dlist_nth(4, &n0, &n3, 4)); +} + +TEST(dlist_nth_circular_and_is_circular) +{ + dnode_t n0 = { INT, { .vi = 0 }, 0, 0 }; + dnode_t n1 = { INT, { .vi = 1 }, 0, 0 }; + dnode_t n2 = { INT, { .vi = 2 }, 0, 0 }; + dnode_t n3 = { INT, { .vi = 3 }, 0, 0 }; + dnode_t n4 = { INT, { .vi = 4 }, 0, 0 }; + dlist_append(&n0, &n1); + dlist_append(&n0, &n2); + dlist_append(&n0, &n3); + dlist_append(&n0, &n4); + dlist_append(&n4, &n0); + + ASSERT(dlist_nth_circular(5, &n4) == &n4); + ASSERT(dlist_is_circular(&n0) == 1); +} + + +int main(int argc, char **argv) +{ + return IHCT_RUN(argc, argv); +} diff --git a/tree.h b/tree.h new file mode 100644 index 0000000..ae72a08 --- /dev/null +++ b/tree.h @@ -0,0 +1,32 @@ +#ifndef TREE_H +#define TREE_H +typedef struct tnode_ tnode_t; +typedef struct tnode_* tnode_ptr; +typedef enum { INT, UINT, CHAR, FLOAT, DOUBLE, CHAR_PTR, VOID_PTR } tnode_value_t; + +typedef struct tnode_ { + int type; + union { + int vi; + unsigned int vui; + char vc; + float vf; + double vd; + char* vcp; + void *vptr; + }; + tnode_ptr parent; + tnode_ptr left_leaf; + tnode_ptr right_leaf; +} tnode_t; + + +tnode_t* tree_root(tnode_t* leaf) +{ + tnode_t* current = leaf; + + while(current = current->parent && current->parent); + return current; +} + +#endif //TREE_H