diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml new file mode 100644 index 0000000..b9d2806 --- /dev/null +++ b/.woodpecker/lint.yml @@ -0,0 +1,16 @@ +variables: + &image 'git.rustybever.be/chewing_bever/c-devop:alpine3.17' + +branches: + exclude: [ main ] +platform: linux/amd64 + +pipeline: + lint: + image: *image + pull: true + commands: + - make lint + - make objs CFLAGS='-Werror -fsyntax-only' + when: + event: [push, pull_request] diff --git a/.woodpecker/test-mem.yml b/.woodpecker/test-mem.yml new file mode 100644 index 0000000..48896a5 --- /dev/null +++ b/.woodpecker/test-mem.yml @@ -0,0 +1,20 @@ +variables: + &image 'git.rustybever.be/chewing_bever/c-devop:alpine3.17' + +branches: + exclude: [ main ] +platform: linux/amd64 + +depends_on: + - test + +pipeline: + test: + image: *image + pull: true + commands: + - make test-mem + - make clean + - make test-mem CFLAGS='-O3 -Werror -Wall' + when: + event: [push, pull_request] diff --git a/.woodpecker.yml b/.woodpecker/test.yml similarity index 58% rename from .woodpecker.yml rename to .woodpecker/test.yml index a7e8fe3..a603923 100644 --- a/.woodpecker.yml +++ b/.woodpecker/test.yml @@ -11,26 +11,12 @@ branches: platform: ${PLATFORM} pipeline: - lint: + build-and-test: image: *image pull: true - commands: - - make lint - when: - event: [push, pull_request] - - build: - image: *image - commands: - - make - - make clean - - CFLAGS='-O3' make - when: - event: [push, pull_request] - - test: - image: *image commands: - make test + - make clean + - make test CFLAGS='-O3 -Werror -Wall' when: event: [push, pull_request] diff --git a/Makefile b/Makefile index 4907163..04c6dd3 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ SRC_DIR ?= src TEST_DIR ?= test INC_DIRS ?= include +LIB := $(BUILD_DIR)/$(LIB_FILENAME) + SRCS != find '$(SRC_DIR)' -iname '*.c' SRCS_H != find $(INC_DIRS) -iname '*.h' SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' @@ -17,6 +19,8 @@ OBJS_TEST := $(SRCS_TEST:%=$(BUILD_DIR)/%.o) DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) BINS_TEST := $(OBJS_TEST:%.c.o=%) +TARGETS_TEST := $(BINS_TEST:%=test-%) +TARGETS_MEM_TEST := $(BINS_TEST:%=test-mem-%) INC_FLAGS := $(addprefix -I,$(INC_DIRS)) @@ -25,42 +29,61 @@ INC_FLAGS := $(addprefix -I,$(INC_DIRS)) # object file is also recompiled if only a header is changed. # -MP: generate a dummy target for every header file (according to the docs it # prevents some errors when removing header files) -CFLAGS ?= -MMD -MP -Wall -Werror -Wextra -CFLAGS += $(INC_FLAGS) +CFLAGS ?= -MMD -MP -g +VIETERCFLAGS := $(INC_FLAGS) $(CFLAGS) -Wall -Wextra .PHONY: all all: vieter # =====COMPILATION===== +# Utility used by the CI to lint +.PHONY: objs +objs: $(OBJS) + .PHONY: vieter -vieter: $(BUILD_DIR)/$(LIB_FILENAME) +vieter: $(LIB) $(BUILD_DIR)/$(LIB_FILENAME): $(OBJS) ar -rcs $@ $(OBJS) $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ + $(CC) $(VIETERCFLAGS) -c $< -o $@ # =====TESTING===== .PHONY: test -test: build-test - @ $(foreach bin,$(BINS_TEST),./$(bin);) +test: $(TARGETS_TEST) + +.PHONY: test-mem +test-mem: $(TARGETS_MEM_TEST) + +.PHONY: $(TARGETS_TEST) +$(TARGETS_TEST): test-%: % + ./$^ + +.PHONY: $(TARGETS_MEM_TEST) +$(TARGETS_MEM_TEST): test-mem-%: % + valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full ./$^ .PHONY: build-test build-test: $(BINS_TEST) # For simplicity, we link every object file to each of the test files. This # might be changed later if this starts to become too slow. -$(BINS_TEST): %: %.c.o $(OBJS) - $(CC) $^ -o $@ +$(BINS_TEST): %: %.c.o $(LIB) + $(CC) \ + $^ -o $@ -# Each test includes the test directory, which contains the acutest header file +# Along with the include directory, each test includes $(TEST_DIR) (which +# contains the acutest.h header file), and the src directory of the module it's +# testing. This allows tests to access internal methods, which aren't publicly +# exposed. $(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c mkdir -p $(dir $@) - $(CC) $(CFLAGS) -I$(TEST_DIR) -c $< -o $@ - + $(CC) $(VIETERCFLAGS) -I$(TEST_DIR) \ + -I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \ + -c $< -o $@ # =====MAINTENANCE===== .PHONY: lint diff --git a/README.md b/README.md index 724ba54..d1fef1a 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,12 @@ # libvieter -This library powers part of Vieter, most noteably the sections that can easily +This library powers part of Vieter, most notably the sections that can easily be implemented in C (or just parts I want to implement in C because it's fun). -The goal of this library is to be completely self-contained, meaning any -required data structures have to be implemented as well. It can only depend on -the C standard libraries. +The goal of this library is to be as self-contained as possible; data +structures should be implemented manually if possible. -Currently it contains the following: - -* Cron expression parser & next time calculator +See the [source code](src) for the list of modules. ## Development @@ -18,13 +15,41 @@ Currently it contains the following: Everything is handled by the provided Makefile. To compile the static library, simply run `make`. +### Project structure + +Each module has its own subdirectory inside `src`, e.g. `src/cron`. This +directory contains the actual implementation of a module, along with any +internally used header files. Each internal function should be defined in a +header file, as to make testing these possible. + +Each module should also have its own header file inside the `include` +directory. This header file defines the public API that the library exposes for +this specific module. + +Any code in a module may only import internal headers from that module, along +with any of the public API header files. Modules should not depend on each +other's internal implementationns. + +Each module should contain a README describing its contents. + +All file names, function names... (even internals) should follow snake case +convention and have a prefix unique to that module, starting with `vieter_`. +For example, the `cron` modules uses the `vieter_cron_` prefix for everything. + +Header files should only import what they explicitely need. If some function is +only used in a .c file, the import should be placed in the .c file instead. + ### Testing This library uses [Acutest](https://github.com/mity/acutest) for its tests. Tests should be placed in the `test` subdirectory, further divided into -directories that correspond those in `src`. Test files should begin with +directories that correspond to those in `src`. Test files should begin with `test_`, and their format should follow the expected format for Acutest. +Each `test_` is compiled separately into a binary, linked with libvieter. A +test file can import any of the public API header files, along with any header +files defined in its respective module. This allows testing internal functions. + To run the tests, simply run `make test`. If you wish to only run a specific test binary, you can find them in `build/test`. diff --git a/include/vieter_cron.h b/include/vieter_cron.h index 9088d4d..459a49f 100644 --- a/include/vieter_cron.h +++ b/include/vieter_cron.h @@ -1,7 +1,6 @@ #ifndef VIETER_CRON #define VIETER_CRON -#include #include #include #include @@ -35,18 +34,38 @@ typedef struct vieter_cron_simple_time { int minute; } vieter_cron_simple_time; -vieter_cron_expression *ce_init(); +/* + * Allocate and initialize a new empty cron expression. + */ +vieter_cron_expression *vieter_cron_expr_init(); +/* + * Deallocate a cron expression. + */ void vieter_cron_expr_free(vieter_cron_expression *ce); +/* + * Given a cron expression and a reference time, calculate the next time after + * the reference time that this expression matches. + */ void vieter_cron_expr_next(vieter_cron_simple_time *out, vieter_cron_expression *ce, vieter_cron_simple_time *ref); +/* + * Convencience wrapper around vieter_cron_expr_next that uses the current time + * as the reference time. + */ void vieter_cron_expr_next_from_now(vieter_cron_simple_time *out, vieter_cron_expression *ce); -enum vieter_cron_parse_error vieter_cron_expr_parse(vieter_cron_expression *out, - const char *expression); +/* + * Try to parse a string into a cron expression. Note that the cron expression + * is updated in-place, meaning it can contain invalid information if the + * function returns an error. The cron expression should only be used if the + * function succeeded. + */ +vieter_cron_parse_error vieter_cron_expr_parse(vieter_cron_expression *out, + const char *expression); #endif diff --git a/include/vieter_heap.h b/include/vieter_heap.h new file mode 100644 index 0000000..ce6f3a4 --- /dev/null +++ b/include/vieter_heap.h @@ -0,0 +1,62 @@ +#ifndef VIETER_HEAP +#define VIETER_HEAP + +#include + +typedef struct vieter_heap vieter_heap; + +typedef enum vieter_heap_error { + vieter_heap_ok = 0, + vieter_heap_empty = 1 +} vieter_heap_error; + +/* + * Allocate and initialize an empty heap. + */ +vieter_heap *vieter_heap_init(); + +/* + * Deallocate a heap. + */ +void vieter_heap_free(vieter_heap *heap); + +/* + * Return how many elements are currently in the heap. + */ +uint64_t vieter_heap_size(vieter_heap *heap); + +/* + * Insert a new value into the heap. + */ +vieter_heap_error vieter_heap_insert(vieter_heap *heap, uint64_t key, + void *data); + +/* + * Remove the smallest element from the heap. + */ +vieter_heap_error vieter_heap_pop(void **out, vieter_heap *heap); + +/* + * Get the smallest element in the heap without removing it. + */ +vieter_heap_error vieter_heap_peek(void **out, vieter_heap *heap); + +/* + * Acquire a read lock on the heap. Return value is the result of + * pthread_rwlock_rdlock. + */ +int vieter_heap_rlock(vieter_heap *heap); + +/* + * Acquire a write lock on the heap. Return value is the result of + * pthread_rwlock_wrlock. + */ +int vieter_heap_wlock(vieter_heap *heap); + +/* + * Unlock the lock after having acquired it. Return value is the result of + * pthread_rwlock_unlock. + */ +int vieter_heap_unlock(vieter_heap *heap); + +#endif diff --git a/src/cron/expression.c b/src/cron/vieter_cron_expression.c similarity index 100% rename from src/cron/expression.c rename to src/cron/vieter_cron_expression.c diff --git a/src/cron/parse.c b/src/cron/vieter_cron_parse.c similarity index 99% rename from src/cron/parse.c rename to src/cron/vieter_cron_parse.c index 5c8da06..a82d1c3 100644 --- a/src/cron/parse.c +++ b/src/cron/vieter_cron_parse.c @@ -1,4 +1,8 @@ #include "vieter_cron.h" +#include +#include +#include +#include // This prefix is needed to properly compile const uint8_t parse_month_days[] = {31, 28, 31, 30, 31, 30, diff --git a/src/heap/README.md b/src/heap/README.md new file mode 100644 index 0000000..8942763 --- /dev/null +++ b/src/heap/README.md @@ -0,0 +1,33 @@ +This min-heap implementation is a pretty standard binomial heap. + +## Representation in memory + +A heap consists of one or more binomial trees, each with a different order `k` +and `2^k` total nodes. This heap can contain `2^64 - 1` elements at most, which +is far more than your memory can contain, but it's still fun to mention. + +A tree does not have its own memory structure; a node that's the root of a +binomial tree is simply called the tree. + +Each node has the following layout: + +```c +typedef struct vieter_heap_node { + uint64_t key; + void *data; + struct vieter_heap_node *largest_order; + union { + struct vieter_heap_node *next_tree; + struct vieter_heap_node *next_largest_order; + } ptr; + uint8_t order; +} vieter_heap_node; +``` + +Each node has a pointer to its child with the largest order (if the node's +order is `0`, this pointer will be NULL). Each non-root node has a pointer to +its sibling with the next-highest order. These pointers allow the children of a +binomial tree to be recombined into a new tree, once their root has been +pop'ed. + +Roots point to the binomial tree in the heap with the next largest order. diff --git a/src/heap/vieter_heap.c b/src/heap/vieter_heap.c new file mode 100644 index 0000000..b10d94c --- /dev/null +++ b/src/heap/vieter_heap.c @@ -0,0 +1,96 @@ +#include "vieter_heap_internal.h" + +#include + +vieter_heap *vieter_heap_init() { + vieter_heap *heap = calloc(1, sizeof(vieter_heap)); + + pthread_rwlock_init(&heap->lock, NULL); + + return heap; +} + +uint64_t vieter_heap_size(vieter_heap *heap) { + uint64_t size = 0; + vieter_heap_node *tree = heap->tree; + + while (tree != NULL) { + size |= (uint64_t)1 << tree->order; + + tree = tree->ptr.next_tree; + } + + return size; +} + +void vieter_heap_free(vieter_heap *heap) { + vieter_heap_node *tree = heap->tree; + vieter_heap_node *next; + + while (tree != NULL) { + next = tree->ptr.next_tree; + vieter_heap_tree_free(tree); + tree = next; + } + + free(heap); +} + +vieter_heap_error vieter_heap_insert(vieter_heap *heap, uint64_t key, + void *data) { + vieter_heap_node *new_tree = vieter_heap_node_init(); + new_tree->key = key; + new_tree->data = data; + new_tree->order = 0; + + if (heap->tree == NULL) { + heap->tree = new_tree; + } else { + heap->tree = vieter_heap_tree_merge(heap->tree, new_tree); + } + + return vieter_heap_ok; +} + +vieter_heap_error vieter_heap_pop(void **out, vieter_heap *heap) { + if (heap->tree == NULL) { + return vieter_heap_empty; + } + + heap->tree = vieter_heap_tree_pop(out, heap->tree); + + return vieter_heap_ok; +} + +vieter_heap_error vieter_heap_peek(void **out, vieter_heap *heap) { + if (heap->tree == NULL) { + return vieter_heap_empty; + } + + vieter_heap_node *tree = heap->tree; + uint64_t smallest_key = tree->key; + *out = tree->data; + + while (tree->ptr.next_tree != NULL) { + tree = tree->ptr.next_tree; + + if (tree->key < smallest_key) { + smallest_key = tree->key; + *out = tree->data; + } + } + + return vieter_heap_ok; +} + +int vieter_heap_rlock(vieter_heap *heap) { + return pthread_rwlock_rdlock(&heap->lock); +} + +int vieter_heap_wlock(vieter_heap *heap) { + return pthread_rwlock_wrlock(&heap->lock); +} + +int vieter_heap_unlock(vieter_heap *heap) { + return pthread_rwlock_unlock(&heap->lock); +} diff --git a/src/heap/vieter_heap_internal.h b/src/heap/vieter_heap_internal.h new file mode 100644 index 0000000..41850e9 --- /dev/null +++ b/src/heap/vieter_heap_internal.h @@ -0,0 +1,8 @@ +#include "vieter_heap.h" +#include "vieter_heap_tree.h" +#include + +struct vieter_heap { + vieter_heap_node *tree; + pthread_rwlock_t lock; +}; diff --git a/src/heap/vieter_heap_tree.c b/src/heap/vieter_heap_tree.c new file mode 100644 index 0000000..10c5437 --- /dev/null +++ b/src/heap/vieter_heap_tree.c @@ -0,0 +1,192 @@ +#include "vieter_heap_tree.h" + +vieter_heap_node *vieter_heap_node_init() { + return calloc(1, sizeof(vieter_heap_node)); +} + +void vieter_heap_node_free(vieter_heap_node *node) { free(node); } + +void vieter_heap_tree_free(vieter_heap_node *root) { + if (root->order == 0) { + goto end; + } + + uint64_t size = 1; + vieter_heap_node **stack = + malloc(((uint64_t)1 << root->order) * sizeof(vieter_heap_node *)); + stack[0] = root->largest_order; + + vieter_heap_node *node; + + while (size > 0) { + node = stack[size - 1]; + size--; + + if (node->largest_order != NULL) { + stack[size] = node->largest_order; + size++; + } + + if (node->ptr.next_largest_order != NULL) { + stack[size] = node->ptr.next_largest_order; + size++; + } + + vieter_heap_node_free(node); + } + + free(stack); + +end: + vieter_heap_node_free(root); +} + +vieter_heap_node *vieter_heap_tree_merge_same_order(vieter_heap_node *root_a, + vieter_heap_node *root_b) { + vieter_heap_node *root, *child; + + if (root_a->key <= root_b->key) { + root = root_a; + child = root_b; + } else { + root = root_b; + child = root_a; + } + + child->ptr.next_largest_order = root->largest_order; + root->largest_order = child; + + root->order++; + + return root; +} + +vieter_heap_node *vieter_heap_tree_merge(vieter_heap_node *target_tree, + vieter_heap_node *other_tree) { + vieter_heap_node *out = target_tree; + + vieter_heap_node *next_other_tree, *next_target_tree; + vieter_heap_node *previous_target_tree = NULL; + + while (target_tree != NULL && other_tree != NULL) { + if (target_tree->order == other_tree->order) { + next_other_tree = other_tree->ptr.next_tree; + next_target_tree = target_tree->ptr.next_tree; + + target_tree = vieter_heap_tree_merge_same_order(target_tree, other_tree); + + target_tree->ptr.next_tree = next_target_tree; + + // If this merge produces a binomial tree whose size is already in + // target, it will be the next target. Therefore, we can merge target's + // trees until we no longer have a duplicate depth. + while (next_target_tree != NULL && + next_target_tree->order == target_tree->order) { + next_target_tree = next_target_tree->ptr.next_tree; + target_tree = vieter_heap_tree_merge_same_order( + target_tree, target_tree->ptr.next_tree); + target_tree->ptr.next_tree = next_target_tree; + } + + if (previous_target_tree != NULL) { + previous_target_tree->ptr.next_tree = target_tree; + } else { + out = target_tree; + } + + other_tree = next_other_tree; + } else if (target_tree->order > other_tree->order) { + next_other_tree = other_tree->ptr.next_tree; + + if (previous_target_tree == NULL) { + previous_target_tree = other_tree; + out = other_tree; + } else { + previous_target_tree->ptr.next_tree = other_tree; + + // This single missing line right here broke this entire function for + // nearly a week. + previous_target_tree = other_tree; + } + + other_tree->ptr.next_tree = target_tree; + other_tree = next_other_tree; + } else { + if (previous_target_tree == NULL) { + out = target_tree; + } + + previous_target_tree = target_tree; + target_tree = target_tree->ptr.next_tree; + } + } + + // Append final part of tree to target + if (target_tree == NULL) { + previous_target_tree->ptr.next_tree = other_tree; + } + + return out; +} + +vieter_heap_node *vieter_heap_tree_pop(void **out, vieter_heap_node *tree) { + vieter_heap_node *tree_before_smallest = NULL; + vieter_heap_node *previous_tree = NULL; + vieter_heap_node *original_root = tree; + + uint64_t smallest_key = tree->key; + + while (tree->ptr.next_tree != NULL) { + previous_tree = tree; + tree = tree->ptr.next_tree; + + if (tree->key < smallest_key) { + smallest_key = tree->key; + tree_before_smallest = previous_tree; + } + } + + vieter_heap_node *tree_to_pop; + + if (tree_before_smallest != NULL) { + tree_to_pop = tree_before_smallest->ptr.next_tree; + tree_before_smallest->ptr.next_tree = tree_to_pop->ptr.next_tree; + } else { + tree_to_pop = original_root; + original_root = original_root->ptr.next_tree; + } + + *out = tree_to_pop->data; + + if (tree_to_pop->order == 0) { + vieter_heap_tree_free(tree_to_pop); + + return original_root; + } + + // Each child has a pointer to its sibling with the next largest order. If we + // want to convert this list of children into their own tree, these pointers + // have to be reversed. + previous_tree = tree_to_pop->largest_order; + vieter_heap_node_free(tree_to_pop); + + tree = previous_tree->ptr.next_largest_order; + previous_tree->ptr.next_tree = NULL; + + vieter_heap_node *next_tree; + + while (tree != NULL) { + next_tree = tree->ptr.next_largest_order; + tree->ptr.next_tree = previous_tree; + + previous_tree = tree; + tree = next_tree; + } + + // original_root is zero if the heap only contained a single tree. + if (original_root != NULL) { + return vieter_heap_tree_merge(original_root, previous_tree); + } else { + return previous_tree; + } +} diff --git a/src/heap/vieter_heap_tree.h b/src/heap/vieter_heap_tree.h new file mode 100644 index 0000000..0a299db --- /dev/null +++ b/src/heap/vieter_heap_tree.h @@ -0,0 +1,53 @@ +#ifndef VIETER_HEAP_TREE +#define VIETER_HEAP_TREE + +#include +#include + +typedef struct vieter_heap_node { + uint64_t key; + void *data; + struct vieter_heap_node *largest_order; + union { + // Roots point to next tree in the heap, other nodes point to their first + // neighbour. + struct vieter_heap_node *next_tree; + struct vieter_heap_node *next_largest_order; + } ptr; + uint8_t order; +} vieter_heap_node; + +/* + * Allocate and initialize a heap node object. + */ +vieter_heap_node *vieter_heap_node_init(); + +/* + * Deallocate a node object. + */ +void vieter_heap_node_free(vieter_heap_node *node); + +/* + * Deallocate a node's entire structure. + */ +void vieter_heap_tree_free(vieter_heap_node *root); + +/* + * Given the roots of the smallest trees in two heaps, merge them into a single + * large heap. + */ +vieter_heap_node *vieter_heap_tree_merge(vieter_heap_node *root_a, vieter_heap_node *root_b); + +/* + * Given the roots of two trees of the same order, merge them into a heap of one + * order larger. + */ +vieter_heap_node *vieter_heap_tree_merge_same_order(vieter_heap_node *root_a, + vieter_heap_node *root_b); + +/* + * Remove the smallest element from the given heap. + */ +vieter_heap_node *vieter_heap_tree_pop(void **out, vieter_heap_node *root); + +#endif diff --git a/test/heap/test_heap.c b/test/heap/test_heap.c new file mode 100644 index 0000000..f77b0dc --- /dev/null +++ b/test/heap/test_heap.c @@ -0,0 +1,187 @@ +#include "acutest.h" +#include "vieter_heap_internal.h" +#include + +#define TEST_SIZE(heap, size) \ + TEST_CHECK(vieter_heap_size(heap) == size); \ + TEST_MSG("Size: %zu, expected: %lu", vieter_heap_size(heap), (uint64_t)size) + +void test_init() { + vieter_heap *heap = vieter_heap_init(); + TEST_CHECK(heap != NULL); + TEST_SIZE(heap, 0); + vieter_heap_free(heap); +} + +void count_nodes(uint64_t *counter, vieter_heap_node *root) { + (*counter)++; + + if (root->largest_order != NULL) { + count_nodes(counter, root->largest_order); + } + + // This will also traverse the various trees + if (root->ptr.next_largest_order != NULL) { + count_nodes(counter, root->ptr.next_largest_order); + } +} + +uint64_t count_nodes_heap(vieter_heap *heap) { + uint64_t counter = 0; + + if (heap->tree != NULL) { + count_nodes(&counter, heap->tree); + } + + return counter; +} + +void test_insert() { + vieter_heap *heap = vieter_heap_init(); + TEST_SIZE(heap, 0); + + void *data; + + for (uint64_t i = 50; i > 0; i--) { + vieter_heap_insert(heap, i, (void *)i); + TEST_SIZE(heap, (uint64_t)51 - i); + TEST_CHECK(count_nodes_heap(heap) == (uint64_t)51 - i); + + data = 0; + + TEST_CHECK(vieter_heap_peek(&data, heap) == vieter_heap_ok); + TEST_CHECK_(data == (void *)i, "%lX == %lX", (uint64_t)data, i); + } + + vieter_heap_free(heap); +} + +void test_insert_random() { + srand(1); + + vieter_heap *heap = vieter_heap_init(); + TEST_SIZE(heap, 0); + + uint64_t num = rand(); + uint64_t smallest = num; + + void *data = NULL; + + for (uint64_t i = 0; i < 5000; i++) { + vieter_heap_insert(heap, num, (void *)num); + TEST_SIZE(heap, i + 1); + TEST_CHECK(count_nodes_heap(heap) == (uint64_t)i + 1); + + if (num < smallest) { + smallest = num; + } + + TEST_CHECK(vieter_heap_peek(&data, heap) == vieter_heap_ok); + TEST_CHECK(data == (void *)smallest); + + data = NULL; + + num = rand(); + } + + vieter_heap_free(heap); +} + +void test_pop() { + const uint64_t n = 500; + + vieter_heap *heap = vieter_heap_init(); + TEST_SIZE(heap, 0); + + void *data; + + for (uint64_t i = n; i > 0; i--) { + vieter_heap_insert(heap, i, (void *)i); + TEST_SIZE(heap, (uint64_t)n + 1 - i); + TEST_CHECK(count_nodes_heap(heap) == (uint64_t)n + 1 - i); + + TEST_CHECK(vieter_heap_peek(&data, heap) == vieter_heap_ok); + TEST_CHECK(data == (void*)i); + } + + data = NULL; + + for (uint64_t i = 1; i <= n; i++) { + TEST_CHECK(vieter_heap_pop(&data, heap) == vieter_heap_ok); + TEST_CHECK(data == (void*)i); + TEST_SIZE(heap, (uint64_t)n - i); + } + + vieter_heap_free(heap); +} + +int uint64_t_compare(const void *a, const void *b) { + if ((*(uint64_t *)a) < (*(uint64_t *)b)) { + return -1; + } else if ((*(uint64_t *)a) > (*(uint64_t *)b)) { + return 1; + } else { + return 0; + } +} + +void test_pop_random() { + const uint64_t n = 500; + + srand(0); + + vieter_heap *heap = vieter_heap_init(); + + uint64_t *numbers = malloc(n * sizeof(uint64_t)); + uint64_t num; + + for (uint64_t i = 0; i < n; i++) { + num = rand(); + vieter_heap_insert(heap, num, (void *)num); + TEST_SIZE(heap, i + 1); + TEST_CHECK(count_nodes_heap(heap) == i + 1); + + numbers[i] = num; + } + + + qsort(numbers, n, sizeof(uint64_t), uint64_t_compare); + + void *data = NULL; + + for (uint64_t i = 0; i < n; i++) { + TEST_CHECK(vieter_heap_peek(&data, heap) == vieter_heap_ok); + TEST_CHECK_(data == (void *)numbers[i], "peek %lx == %lx", (uint64_t)data, numbers[i]); + + data = NULL; + + TEST_CHECK(vieter_heap_pop(&data, heap) == vieter_heap_ok); + TEST_CHECK_(data == (void *)numbers[i], "pop %lx == %lx", (uint64_t)data, numbers[i]); + TEST_SIZE(heap, n - i - 1); + TEST_CHECK(count_nodes_heap(heap) == n - i - 1); + + // Assure each size is also a valid heap after inserting + vieter_heap_insert(heap, numbers[i], (void *)numbers[i]); + TEST_SIZE(heap, n - i); + TEST_CHECK(count_nodes_heap(heap) == n - i); + + data = NULL; + + TEST_CHECK(vieter_heap_pop(&data, heap) == vieter_heap_ok); + TEST_CHECK_(data == (void *)numbers[i], "pop %lx == %lx", (uint64_t)data, numbers[i]); + TEST_SIZE(heap, n - i - 1); + TEST_CHECK(count_nodes_heap(heap) == n - i - 1); + } + + vieter_heap_free(heap); + free(numbers); +} + +TEST_LIST = { + {"init", test_init}, + {"insert", test_insert}, + {"insert random", test_insert_random}, + {"pop", test_pop}, + {"pop random", test_pop_random}, + {NULL, NULL} +}; diff --git a/test/heap/test_merge.c b/test/heap/test_merge.c new file mode 100644 index 0000000..10053f2 --- /dev/null +++ b/test/heap/test_merge.c @@ -0,0 +1,28 @@ +#include "acutest.h" +#include "vieter_heap.h" +#include "vieter_heap_tree.h" +#include + +void test_merge_same_order() { + vieter_heap_node *root_a = vieter_heap_node_init(); + root_a->key = 1; + root_a->order = 0; + + vieter_heap_node *root_b = vieter_heap_node_init(); + root_b->key = 2; + root_b->order = 0; + + vieter_heap_node *merged = vieter_heap_tree_merge_same_order(root_a, root_b); + + TEST_CHECK(merged == root_a); + TEST_CHECK(merged->key == 1); + TEST_CHECK(merged->largest_order == root_b); + TEST_CHECK(merged->ptr.next_largest_order == NULL); + + vieter_heap_tree_free(merged); +} + +TEST_LIST = { + {"merge same order", test_merge_same_order}, + {NULL, NULL} +};