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..ac56c97 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,66 @@ 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. +<<<<<<< HEAD +$(BINS_TEST): %: %.c.o $(LIB) + $(CC) \ + $^ -o $@ +======= $(BINS_TEST): %: %.c.o $(OBJS) - $(CC) $^ -o $@ + $(CC) $^ -larchive -o $@ +>>>>>>> c94ab92 (refactor: Add libarchive link to test compilation area of the Makefile. Created test units with xcursor-dmz as test package.) -# 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/dynarray.h b/include/dynarray.h new file mode 100644 index 0000000..2ab4022 --- /dev/null +++ b/include/dynarray.h @@ -0,0 +1,24 @@ +#ifndef VIETER_DYNARRAY +#define VIETER_DYNARRAY + +#include +#include + +typedef struct dyn_array DynArray; +struct dyn_array { + char **array; + size_t capacity; + size_t size; +}; + +DynArray *dynarray_init(size_t initial_capacity); +void dynarray_add(DynArray *da, const char * s); +void dynarray_free(DynArray *da); + +/** + * Convert a DynArray into an array by freeing all its surrounding components + * and returning the underlying array pointer. + */ +char **dynarray_convert(DynArray *da); + +#endif diff --git a/include/package.h b/include/package.h new file mode 100644 index 0000000..09e91bd --- /dev/null +++ b/include/package.h @@ -0,0 +1,26 @@ +#ifndef VIETER_PACKAGE +#define VIETER_PACKAGE + +#include +#include +#include +#include + +#include "archive.h" +#include "archive_entry.h" + +#include "package_info.h" +#include "dynarray.h" + +typedef struct pkg { + char *path; + PkgInfo *info; + DynArray *files; + int compression; +} Pkg; + +Pkg *package_read_archive(const char *pkg_path); +void package_free(Pkg ** ptp); +char *package_to_description(Pkg *pkg); + +#endif diff --git a/include/package_info.h b/include/package_info.h new file mode 100644 index 0000000..b1388a2 --- /dev/null +++ b/include/package_info.h @@ -0,0 +1,39 @@ +#ifndef VIETER_PACKAGE_INFO +#define VIETER_PACKAGE_INFO + +#define FREE_STRING(sp) if (sp != NULL) free(sp) + +#include + +#include "dynarray.h" + +typedef struct pkg_info { + char *name; + char *base; + char *version; + char *description; + int64_t size; + int64_t csize; + char *url; + char *arch; + int64_t build_date; + char *packager; + char *pgpsig; + int64_t pgpsigsize; + + DynArray *groups; + DynArray *licenses; + DynArray *replaces; + DynArray *depends; + DynArray *conflicts; + DynArray *provides; + DynArray *optdepends; + DynArray *makedepends; + DynArray *checkdepends; +} PkgInfo; + +PkgInfo *package_info_init(); +void package_info_parse(PkgInfo *pkg_info, char *pkg_info_str); +void package_info_free(PkgInfo *pkg_info); + +#endif 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/src/package/dynarray.c b/src/package/dynarray.c new file mode 100644 index 0000000..a78e7ff --- /dev/null +++ b/src/package/dynarray.c @@ -0,0 +1,50 @@ +#include "dynarray.h" + +DynArray *dynarray_init(size_t initial_capacity) { + DynArray *da = malloc(sizeof(DynArray)); + da->size = 0; + da->capacity = initial_capacity; + + return da; +} + +void dynarray_add(DynArray *da, const char *s) { + // An empty dynarray does not have an allocated internal array yet + if (da->size == 0) { + da->array = malloc(sizeof(char*) * da->capacity); + } + // Double array size if it's full + else if (da->size == da->capacity) { + // if the realloc fails, access to memory in da->array is lost + da->array = realloc(da->array, sizeof(char*) * da->capacity * 2); + da->capacity *= 2; + } + + da->array[da->size] = strdup(s); + da->size++; +} + +void dynarray_free(DynArray *da) { + if (da == NULL) { + return; + } + + if (da->array != NULL) { + for (size_t i = 0; i < da->size; i++) { + free(da->array[i]); + } + + free(da->array); + } + + free(da); +} + +char **dynarray_convert(DynArray *da) { + char **array = da->array; + + da->array = NULL; + dynarray_free(da); + + return array; +} diff --git a/src/package/package.c b/src/package/package.c new file mode 100644 index 0000000..8d1565d --- /dev/null +++ b/src/package/package.c @@ -0,0 +1,180 @@ +#include "package.h" + +#define SMALL_BUFF_SIZE 128 + +#define ADD_STRING(section, field) if (pkg_info->field != 0) { \ + snprintf(aux, SMALL_BUFF_SIZE, section, pkg_info->field); \ + if (buff_size < strlen(description) + SMALL_BUFF_SIZE + 1) { \ + description = realloc(description, buff_size * 2); \ + buff_size *= 2; \ + } \ + strcat(description, aux); \ +} +#define ADD_ARRAY(section, field) i = 0; if (pkg_info->field != NULL) { \ + snprintf(aux, SMALL_BUFF_SIZE, section, pkg_info->field->array[i]); i++; \ + if (buff_size < strlen(description) + SMALL_BUFF_SIZE + 1) { \ + description = realloc(description, buff_size * 2); \ + buff_size *= 2; \ + } \ + strcat(description, aux); \ + while (pkg_info->field->array[i] != NULL) { \ + snprintf(aux, SMALL_BUFF_SIZE, "\n%s", pkg_info->field->array[i]); i++; \ + if (buff_size < strlen(description) + SMALL_BUFF_SIZE + 1) { \ + description = realloc(description, buff_size * 2); \ + buff_size *= 2; \ + } \ + strcat(description, aux); \ + } \ +} + +static char *ignored_names[5] = { + ".BUILDINFO", + ".INSTALL", + ".MTREE", + ".PKGINFO", + ".CHANGELOG" +}; +static size_t ignored_words_len = sizeof(ignored_names) / sizeof(char *); + +Pkg *package_init() { + return calloc(sizeof(PkgInfo), 1); +} + +Pkg *package_read_archive(const char *pkg_path) { + struct archive *a = archive_read_new(); + struct archive_entry *entry = archive_entry_new(); + + // These three are the most commonly used compression methods + archive_read_support_filter_zstd(a); + archive_read_support_filter_gzip(a); + archive_read_support_filter_xz(a); + + // Contents should always be a tarball + archive_read_support_format_tar(a); + + // TODO where does this 10240 come from? + int r = archive_read_open_filename(a, pkg_path, 10240); + + // Exit early if we weren't able to successfully open the archive for reading + if (r != ARCHIVE_OK) { + return NULL; + } + + int compression_code = archive_filter_code(a, 0); + const char *path_name; + + PkgInfo *pkg_info; + DynArray *files = dynarray_init(16); + dynarray_add(files, "%FILES%"); + + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + path_name = archive_entry_pathname(entry); + + bool ignore = false; + + for (size_t i = 0; i < ignored_words_len; i++) { + if (strcmp(path_name, ignored_names[i]) == 0) { + ignore = true; + break; + } + } + + if (!ignore) { + dynarray_add(files, path_name); + } + + if (strcmp(path_name, ".PKGINFO") == 0) { + // Read data of file into memory buffer + int size = archive_entry_size(entry); + char *buf = malloc(size); + archive_read_data(a, buf, size); + + // Parse package info string into a struct + pkg_info = package_info_init(); + package_info_parse(pkg_info, buf); + + free(buf); + } else { + archive_read_data_skip(a); + } + } + + // Get size of file + struct stat stats; + + if (stat(pkg_path, &stats) != 0) { + return NULL; + } + + pkg_info->csize = stats.st_size; + + archive_read_free(a); + + // Create final return value + Pkg *pkg = package_init(); + pkg->path = strdup(pkg_path); + pkg->info = pkg_info; + pkg->files = files; + pkg->compression = compression_code; + + return pkg; +} + +void sha256sum(Pkg *pkg, char *res) { + char command[SMALL_BUFF_SIZE]; + snprintf(command, SMALL_BUFF_SIZE, "sha256sum %s", pkg->path); + FILE *p = popen(command, "r"); + + fgets(res, 65, p); + pclose(p); +} + +char *package_to_description(Pkg *pkg) { + PkgInfo *pkg_info = pkg->info; + + size_t buff_size = 1024; + char aux[SMALL_BUFF_SIZE]; + char *description = malloc(sizeof(char) * buff_size); + int i; + + // special case for FILENAME + // assuming .pkg.tar.zst; other formats are valid, this should account for that + snprintf(aux, SMALL_BUFF_SIZE, "%%FILENAME%%\n%s-%s-%s.pkg.tar.zst", pkg_info->name, pkg_info->version, + pkg_info->arch); + strcat(description, aux); + + ADD_STRING("\n\n%%NAME%%\n%s", name); + ADD_STRING("\n\n%%BASE%%\n%s", base); + ADD_STRING("\n\n%%VERSION%%\n%s", version); + ADD_STRING("\n\n%%DESC%%\n%s", description); + ADD_ARRAY("\n\n%%GROUPS%%\n%s", groups); + ADD_STRING("\n\n%%CSIZE%%\n%ld", csize); + ADD_STRING("\n\n%%ISIZE%%\n%ld", size); + + char checksum[65]; + sha256sum(pkg, checksum); + snprintf(aux, SMALL_BUFF_SIZE, "\n\n%%SHA256SUM%%\n%s", checksum); + if (buff_size < strlen(description) + SMALL_BUFF_SIZE + 1) { + description = realloc(description, buff_size * 2); + buff_size *= 2; + } + strcat(description, aux); + + ADD_STRING("\n\n%%URL%%\n%s", url); + ADD_ARRAY("\n\n%%LICENSE%%\n%s", licenses); + ADD_STRING("\n\n%%ARCH%%\n%s", arch); + ADD_STRING("\n\n%%BUILDDATE%%\n%ld", build_date); + ADD_STRING("\n\n%%PACKAGER%%\n%s", packager); + ADD_ARRAY("\n\n%%REPLACES%%\n%s", replaces); + ADD_ARRAY("\n\n%%CONFLICTS%%\n%s", conflicts); + ADD_ARRAY("\n\n%%PROVIDES%%\n%s", provides); + ADD_ARRAY("\n\n%%DEPENDS%%\n%s", depends); + ADD_ARRAY("\n\n%%OPTDEPENDS%%\n%s", optdepends); + ADD_ARRAY("\n\n%%MAKEDEPENDS%%\n%s", makedepends); + ADD_ARRAY("\n\n%%CHECKDEPENDS%%\n%s", checkdepends); + + snprintf(aux, SMALL_BUFF_SIZE, "\n\n"); + strcat(description, aux); + + return description; +} diff --git a/src/package/package_info.c b/src/package/package_info.c new file mode 100644 index 0000000..5e1986d --- /dev/null +++ b/src/package/package_info.c @@ -0,0 +1,79 @@ +#include + +#include "package_info.h" + +#define PKG_INFO_STRING(key_ptr, field) if ((value_ptr = strstr(value_ptr, key_ptr)) != NULL) { \ + value_ptr += strlen(key_ptr);\ + tail_ptr = strchr(value_ptr, '\n');\ + tail_ptr[0] = '\0'; \ + pkg_info->field = strdup(value_ptr); \ + tail_ptr[0] = '\n'; \ +} value_ptr = tail_ptr; + +#define PKG_INFO_INT(key_ptr, field) value_ptr = strstr(value_ptr, key_ptr) + strlen(key_ptr);\ + tail_ptr = strchr(value_ptr, '\n');\ + tail_ptr[0] = '\0'; \ + pkg_info->field = atoi(value_ptr); \ + tail_ptr[0] = '\n'; \ + value_ptr = tail_ptr; + +#define PKG_INFO_ARRAY(key_ptr, field) while((value_ptr = strstr(value_ptr, key_ptr)) != NULL){ \ + value_ptr = value_ptr + strlen(key_ptr);\ + tail_ptr = strchr(value_ptr, '\n'); \ + tail_ptr[0] = '\0'; \ + if(pkg_info->field == NULL) { pkg_info->field = dynarray_init(4); } \ + dynarray_add(pkg_info->field, value_ptr); \ + tail_ptr[0] = '\n'; \ + value_ptr = tail_ptr;\ +} value_ptr = tail_ptr; + +PkgInfo *package_info_init() { + return calloc(1, sizeof(PkgInfo)); +} + +void package_info_free(PkgInfo *pkg_info) { + FREE_STRING(pkg_info->name); + FREE_STRING(pkg_info->base); + FREE_STRING(pkg_info->version); + FREE_STRING(pkg_info->description); + FREE_STRING(pkg_info->url); + FREE_STRING(pkg_info->arch); + FREE_STRING(pkg_info->packager); + FREE_STRING(pkg_info->pgpsig); + + dynarray_free(pkg_info->groups); + dynarray_free(pkg_info->licenses); + dynarray_free(pkg_info->replaces); + dynarray_free(pkg_info->depends); + dynarray_free(pkg_info->conflicts); + dynarray_free(pkg_info->provides); + dynarray_free(pkg_info->optdepends); + dynarray_free(pkg_info->makedepends); + dynarray_free(pkg_info->checkdepends); + + free(pkg_info); +} + +void package_info_parse(PkgInfo *pkg_info, char *pkg_info_str) { + char *value_ptr = pkg_info_str, *tail_ptr; + + PKG_INFO_STRING("\npkgname = ", name); + PKG_INFO_STRING("\npkgbase = ", base); + PKG_INFO_STRING("\npkgver = ", version); + PKG_INFO_STRING("\npkgdesc = ", description); + PKG_INFO_STRING("\nurl = ", url); + PKG_INFO_INT("\nbuilddate = ", build_date); + PKG_INFO_STRING("\npackager = ", packager); + PKG_INFO_INT("\nsize = ", size); + PKG_INFO_STRING("\narch = ", arch); + PKG_INFO_ARRAY("\nlicense = ", licenses); + PKG_INFO_ARRAY("\nreplaces = ", replaces); + PKG_INFO_ARRAY("\ngroup = ", groups); + PKG_INFO_ARRAY("\nconflict = ", conflicts); + PKG_INFO_ARRAY("\nprovides = ", provides); + PKG_INFO_ARRAY("\ndepend = ", depends); + PKG_INFO_ARRAY("\noptdepend = ", optdepends); + PKG_INFO_ARRAY("\nmakedepend = ", makedepends); + PKG_INFO_ARRAY("\ncheckdepend = ", checkdepends); + +} 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} +}; diff --git a/test/package/.PKGINFO b/test/package/.PKGINFO new file mode 100644 index 0000000..7113061 --- /dev/null +++ b/test/package/.PKGINFO @@ -0,0 +1,22 @@ +# Generated by makepkg 6.0.2 +# using fakeroot version 1.30.1 +pkgname = xcursor-dmz +pkgbase = xcursor-dmz +pkgver = 0.4.5-2 +pkgdesc = Style neutral, scalable cursor theme +url = https://packages.debian.org/sid/dmz-cursor-theme +builddate = 1673751613 +packager = Unknown Packager +size = 3469584 +arch = any +license = MIT +replaces = test1 +group = x11 +conflict = test2 +conflict = test3 +provides = test4 +depend = test5 +depend = test6 +optdepend = test7 +makedepend = xorg-xcursorgen +checkdepend = test8 diff --git a/test/package/desc b/test/package/desc new file mode 100644 index 0000000..d583af1 --- /dev/null +++ b/test/package/desc @@ -0,0 +1,45 @@ +%FILENAME% +xcursor-dmz-0.4.5-2-any.pkg.tar.zst + +%NAME% +xcursor-dmz + +%BASE% +xcursor-dmz + +%VERSION% +0.4.5-2 + +%DESC% +Style neutral, scalable cursor theme + +%GROUPS% +x11 + +%CSIZE% +328282 + +%ISIZE% +3469584 + +%SHA256SUM% +4f4bce9e975334ed7775ff4ddf4d2e82e411d599802f6179a122f89149f53bfb + +%URL% +https://packages.debian.org/sid/dmz-cursor-theme + +%LICENSE% +MIT + +%ARCH% +any + +%BUILDDATE% +1673751613 + +%PACKAGER% +Unknown Packager + +%MAKEDEPENDS% +xorg-xcursorgen + diff --git a/test/package/files b/test/package/files new file mode 100644 index 0000000..aab44b1 --- /dev/null +++ b/test/package/files @@ -0,0 +1,243 @@ +%FILES% +usr/ +usr/share/ +usr/share/icons/ +usr/share/icons/DMZ-Black/ +usr/share/icons/DMZ-Black/cursor.theme +usr/share/icons/DMZ-Black/cursors/ +usr/share/icons/DMZ-Black/cursors/00008160000006810000408080010102 +usr/share/icons/DMZ-Black/cursors/028006030e0e7ebffc7f7070c0600140 +usr/share/icons/DMZ-Black/cursors/03b6e0fcb3499374a867c041f52298f0 +usr/share/icons/DMZ-Black/cursors/08e8e1c95fe2fc01f976f1e063a24ccd +usr/share/icons/DMZ-Black/cursors/1081e37283d90000800003c07f3ef6bf +usr/share/icons/DMZ-Black/cursors/14fef782d02440884392942c11205230 +usr/share/icons/DMZ-Black/cursors/2870a09082c103050810ffdffffe0204 +usr/share/icons/DMZ-Black/cursors/3085a0e285430894940527032f8b26df +usr/share/icons/DMZ-Black/cursors/3ecb610c1bf2410f44200f48c40d3599 +usr/share/icons/DMZ-Black/cursors/4498f0e0c1937ffe01fd06f973665830 +usr/share/icons/DMZ-Black/cursors/5c6cd98b3f3ebcb1f9c7f1c204630408 +usr/share/icons/DMZ-Black/cursors/6407b0e94181790501fd1e167b474872 +usr/share/icons/DMZ-Black/cursors/640fb0e74195791501fd1ed57b41487f +usr/share/icons/DMZ-Black/cursors/9081237383d90e509aa00f00170e968f +usr/share/icons/DMZ-Black/cursors/9d800788f1b08800ae810202380a0822 +usr/share/icons/DMZ-Black/cursors/X_cursor +usr/share/icons/DMZ-Black/cursors/alias +usr/share/icons/DMZ-Black/cursors/arrow +usr/share/icons/DMZ-Black/cursors/bd_double_arrow +usr/share/icons/DMZ-Black/cursors/bottom_left_corner +usr/share/icons/DMZ-Black/cursors/bottom_right_corner +usr/share/icons/DMZ-Black/cursors/bottom_side +usr/share/icons/DMZ-Black/cursors/bottom_tee +usr/share/icons/DMZ-Black/cursors/c7088f0f3e6c8088236ef8e1e3e70000 +usr/share/icons/DMZ-Black/cursors/circle +usr/share/icons/DMZ-Black/cursors/col-resize +usr/share/icons/DMZ-Black/cursors/color-picker +usr/share/icons/DMZ-Black/cursors/copy +usr/share/icons/DMZ-Black/cursors/cross +usr/share/icons/DMZ-Black/cursors/cross_reverse +usr/share/icons/DMZ-Black/cursors/crossed_circle +usr/share/icons/DMZ-Black/cursors/crosshair +usr/share/icons/DMZ-Black/cursors/d9ce0ab605698f320427677b458ad60b +usr/share/icons/DMZ-Black/cursors/default +usr/share/icons/DMZ-Black/cursors/diamond_cross +usr/share/icons/DMZ-Black/cursors/dnd-ask +usr/share/icons/DMZ-Black/cursors/dnd-copy +usr/share/icons/DMZ-Black/cursors/dnd-link +usr/share/icons/DMZ-Black/cursors/dnd-move +usr/share/icons/DMZ-Black/cursors/dnd-none +usr/share/icons/DMZ-Black/cursors/dot_box_mask +usr/share/icons/DMZ-Black/cursors/dotbox +usr/share/icons/DMZ-Black/cursors/double_arrow +usr/share/icons/DMZ-Black/cursors/draft_large +usr/share/icons/DMZ-Black/cursors/draft_small +usr/share/icons/DMZ-Black/cursors/draped_box +usr/share/icons/DMZ-Black/cursors/e-resize +usr/share/icons/DMZ-Black/cursors/e29285e634086352946a0e7090d73106 +usr/share/icons/DMZ-Black/cursors/ew-resize +usr/share/icons/DMZ-Black/cursors/fcf1c3c7cd4491d801f1e1c78f100000 +usr/share/icons/DMZ-Black/cursors/fd_double_arrow +usr/share/icons/DMZ-Black/cursors/fleur +usr/share/icons/DMZ-Black/cursors/grab +usr/share/icons/DMZ-Black/cursors/grabbing +usr/share/icons/DMZ-Black/cursors/h_double_arrow +usr/share/icons/DMZ-Black/cursors/hand +usr/share/icons/DMZ-Black/cursors/hand1 +usr/share/icons/DMZ-Black/cursors/hand2 +usr/share/icons/DMZ-Black/cursors/help +usr/share/icons/DMZ-Black/cursors/icon +usr/share/icons/DMZ-Black/cursors/left_ptr +usr/share/icons/DMZ-Black/cursors/left_ptr_help +usr/share/icons/DMZ-Black/cursors/left_ptr_watch +usr/share/icons/DMZ-Black/cursors/left_side +usr/share/icons/DMZ-Black/cursors/left_tee +usr/share/icons/DMZ-Black/cursors/link +usr/share/icons/DMZ-Black/cursors/ll_angle +usr/share/icons/DMZ-Black/cursors/lr_angle +usr/share/icons/DMZ-Black/cursors/move +usr/share/icons/DMZ-Black/cursors/n-resize +usr/share/icons/DMZ-Black/cursors/ne-resize +usr/share/icons/DMZ-Black/cursors/nesw-resize +usr/share/icons/DMZ-Black/cursors/not-allowed +usr/share/icons/DMZ-Black/cursors/ns-resize +usr/share/icons/DMZ-Black/cursors/nw-resize +usr/share/icons/DMZ-Black/cursors/nwse-resize +usr/share/icons/DMZ-Black/cursors/openhand +usr/share/icons/DMZ-Black/cursors/pencil +usr/share/icons/DMZ-Black/cursors/pirate +usr/share/icons/DMZ-Black/cursors/plus +usr/share/icons/DMZ-Black/cursors/pointer +usr/share/icons/DMZ-Black/cursors/progress +usr/share/icons/DMZ-Black/cursors/question_arrow +usr/share/icons/DMZ-Black/cursors/right_ptr +usr/share/icons/DMZ-Black/cursors/right_side +usr/share/icons/DMZ-Black/cursors/right_tee +usr/share/icons/DMZ-Black/cursors/row-resize +usr/share/icons/DMZ-Black/cursors/s-resize +usr/share/icons/DMZ-Black/cursors/sb_down_arrow +usr/share/icons/DMZ-Black/cursors/sb_h_double_arrow +usr/share/icons/DMZ-Black/cursors/sb_left_arrow +usr/share/icons/DMZ-Black/cursors/sb_right_arrow +usr/share/icons/DMZ-Black/cursors/sb_up_arrow +usr/share/icons/DMZ-Black/cursors/sb_v_double_arrow +usr/share/icons/DMZ-Black/cursors/se-resize +usr/share/icons/DMZ-Black/cursors/size_bdiag +usr/share/icons/DMZ-Black/cursors/size_fdiag +usr/share/icons/DMZ-Black/cursors/size_hor +usr/share/icons/DMZ-Black/cursors/size_ver +usr/share/icons/DMZ-Black/cursors/sw-resize +usr/share/icons/DMZ-Black/cursors/target +usr/share/icons/DMZ-Black/cursors/tcross +usr/share/icons/DMZ-Black/cursors/text +usr/share/icons/DMZ-Black/cursors/top_left_arrow +usr/share/icons/DMZ-Black/cursors/top_left_corner +usr/share/icons/DMZ-Black/cursors/top_right_corner +usr/share/icons/DMZ-Black/cursors/top_side +usr/share/icons/DMZ-Black/cursors/top_tee +usr/share/icons/DMZ-Black/cursors/ul_angle +usr/share/icons/DMZ-Black/cursors/ur_angle +usr/share/icons/DMZ-Black/cursors/v_double_arrow +usr/share/icons/DMZ-Black/cursors/w-resize +usr/share/icons/DMZ-Black/cursors/wait +usr/share/icons/DMZ-Black/cursors/watch +usr/share/icons/DMZ-Black/cursors/xterm +usr/share/icons/DMZ-White/ +usr/share/icons/DMZ-White/cursor.theme +usr/share/icons/DMZ-White/cursors/ +usr/share/icons/DMZ-White/cursors/00008160000006810000408080010102 +usr/share/icons/DMZ-White/cursors/028006030e0e7ebffc7f7070c0600140 +usr/share/icons/DMZ-White/cursors/03b6e0fcb3499374a867c041f52298f0 +usr/share/icons/DMZ-White/cursors/08e8e1c95fe2fc01f976f1e063a24ccd +usr/share/icons/DMZ-White/cursors/1081e37283d90000800003c07f3ef6bf +usr/share/icons/DMZ-White/cursors/14fef782d02440884392942c11205230 +usr/share/icons/DMZ-White/cursors/2870a09082c103050810ffdffffe0204 +usr/share/icons/DMZ-White/cursors/3085a0e285430894940527032f8b26df +usr/share/icons/DMZ-White/cursors/3ecb610c1bf2410f44200f48c40d3599 +usr/share/icons/DMZ-White/cursors/4498f0e0c1937ffe01fd06f973665830 +usr/share/icons/DMZ-White/cursors/5c6cd98b3f3ebcb1f9c7f1c204630408 +usr/share/icons/DMZ-White/cursors/6407b0e94181790501fd1e167b474872 +usr/share/icons/DMZ-White/cursors/640fb0e74195791501fd1ed57b41487f +usr/share/icons/DMZ-White/cursors/9081237383d90e509aa00f00170e968f +usr/share/icons/DMZ-White/cursors/9d800788f1b08800ae810202380a0822 +usr/share/icons/DMZ-White/cursors/X_cursor +usr/share/icons/DMZ-White/cursors/alias +usr/share/icons/DMZ-White/cursors/arrow +usr/share/icons/DMZ-White/cursors/bd_double_arrow +usr/share/icons/DMZ-White/cursors/bottom_left_corner +usr/share/icons/DMZ-White/cursors/bottom_right_corner +usr/share/icons/DMZ-White/cursors/bottom_side +usr/share/icons/DMZ-White/cursors/bottom_tee +usr/share/icons/DMZ-White/cursors/c7088f0f3e6c8088236ef8e1e3e70000 +usr/share/icons/DMZ-White/cursors/circle +usr/share/icons/DMZ-White/cursors/col-resize +usr/share/icons/DMZ-White/cursors/color-picker +usr/share/icons/DMZ-White/cursors/copy +usr/share/icons/DMZ-White/cursors/cross +usr/share/icons/DMZ-White/cursors/cross_reverse +usr/share/icons/DMZ-White/cursors/crossed_circle +usr/share/icons/DMZ-White/cursors/crosshair +usr/share/icons/DMZ-White/cursors/d9ce0ab605698f320427677b458ad60b +usr/share/icons/DMZ-White/cursors/default +usr/share/icons/DMZ-White/cursors/diamond_cross +usr/share/icons/DMZ-White/cursors/dnd-ask +usr/share/icons/DMZ-White/cursors/dnd-copy +usr/share/icons/DMZ-White/cursors/dnd-link +usr/share/icons/DMZ-White/cursors/dnd-move +usr/share/icons/DMZ-White/cursors/dnd-none +usr/share/icons/DMZ-White/cursors/dot_box_mask +usr/share/icons/DMZ-White/cursors/dotbox +usr/share/icons/DMZ-White/cursors/double_arrow +usr/share/icons/DMZ-White/cursors/draft_large +usr/share/icons/DMZ-White/cursors/draft_small +usr/share/icons/DMZ-White/cursors/draped_box +usr/share/icons/DMZ-White/cursors/e-resize +usr/share/icons/DMZ-White/cursors/e29285e634086352946a0e7090d73106 +usr/share/icons/DMZ-White/cursors/ew-resize +usr/share/icons/DMZ-White/cursors/fcf1c3c7cd4491d801f1e1c78f100000 +usr/share/icons/DMZ-White/cursors/fd_double_arrow +usr/share/icons/DMZ-White/cursors/fleur +usr/share/icons/DMZ-White/cursors/grab +usr/share/icons/DMZ-White/cursors/grabbing +usr/share/icons/DMZ-White/cursors/h_double_arrow +usr/share/icons/DMZ-White/cursors/hand +usr/share/icons/DMZ-White/cursors/hand1 +usr/share/icons/DMZ-White/cursors/hand2 +usr/share/icons/DMZ-White/cursors/help +usr/share/icons/DMZ-White/cursors/icon +usr/share/icons/DMZ-White/cursors/left_ptr +usr/share/icons/DMZ-White/cursors/left_ptr_help +usr/share/icons/DMZ-White/cursors/left_ptr_watch +usr/share/icons/DMZ-White/cursors/left_side +usr/share/icons/DMZ-White/cursors/left_tee +usr/share/icons/DMZ-White/cursors/link +usr/share/icons/DMZ-White/cursors/ll_angle +usr/share/icons/DMZ-White/cursors/lr_angle +usr/share/icons/DMZ-White/cursors/move +usr/share/icons/DMZ-White/cursors/n-resize +usr/share/icons/DMZ-White/cursors/ne-resize +usr/share/icons/DMZ-White/cursors/nesw-resize +usr/share/icons/DMZ-White/cursors/not-allowed +usr/share/icons/DMZ-White/cursors/ns-resize +usr/share/icons/DMZ-White/cursors/nw-resize +usr/share/icons/DMZ-White/cursors/nwse-resize +usr/share/icons/DMZ-White/cursors/openhand +usr/share/icons/DMZ-White/cursors/pencil +usr/share/icons/DMZ-White/cursors/pirate +usr/share/icons/DMZ-White/cursors/plus +usr/share/icons/DMZ-White/cursors/pointer +usr/share/icons/DMZ-White/cursors/progress +usr/share/icons/DMZ-White/cursors/question_arrow +usr/share/icons/DMZ-White/cursors/right_ptr +usr/share/icons/DMZ-White/cursors/right_side +usr/share/icons/DMZ-White/cursors/right_tee +usr/share/icons/DMZ-White/cursors/row-resize +usr/share/icons/DMZ-White/cursors/s-resize +usr/share/icons/DMZ-White/cursors/sb_down_arrow +usr/share/icons/DMZ-White/cursors/sb_h_double_arrow +usr/share/icons/DMZ-White/cursors/sb_left_arrow +usr/share/icons/DMZ-White/cursors/sb_right_arrow +usr/share/icons/DMZ-White/cursors/sb_up_arrow +usr/share/icons/DMZ-White/cursors/sb_v_double_arrow +usr/share/icons/DMZ-White/cursors/se-resize +usr/share/icons/DMZ-White/cursors/size_bdiag +usr/share/icons/DMZ-White/cursors/size_fdiag +usr/share/icons/DMZ-White/cursors/size_hor +usr/share/icons/DMZ-White/cursors/size_ver +usr/share/icons/DMZ-White/cursors/sw-resize +usr/share/icons/DMZ-White/cursors/target +usr/share/icons/DMZ-White/cursors/tcross +usr/share/icons/DMZ-White/cursors/text +usr/share/icons/DMZ-White/cursors/top_left_arrow +usr/share/icons/DMZ-White/cursors/top_left_corner +usr/share/icons/DMZ-White/cursors/top_right_corner +usr/share/icons/DMZ-White/cursors/top_side +usr/share/icons/DMZ-White/cursors/top_tee +usr/share/icons/DMZ-White/cursors/ul_angle +usr/share/icons/DMZ-White/cursors/ur_angle +usr/share/icons/DMZ-White/cursors/v_double_arrow +usr/share/icons/DMZ-White/cursors/w-resize +usr/share/icons/DMZ-White/cursors/wait +usr/share/icons/DMZ-White/cursors/watch +usr/share/icons/DMZ-White/cursors/xterm +usr/share/licenses/ +usr/share/licenses/xcursor-dmz/ +usr/share/licenses/xcursor-dmz/LICENSE diff --git a/test/package/test_package.c b/test/package/test_package.c new file mode 100644 index 0000000..c0c7085 --- /dev/null +++ b/test/package/test_package.c @@ -0,0 +1,84 @@ +#include "acutest.h" +#include "package.h" + +void test_pkg_info_parse() { + FILE *f = fopen("./test/package/.PKGINFO", "r"); + TEST_ASSERT_(f != NULL, "could not find test .PKGINFO file in ./test/package "); + fseek(f, 0L, SEEK_END); + size_t size = ftell(f); + fflush(stdout); + rewind(f); + char *pkg_info_str = malloc(size); + fread(pkg_info_str, 1, size, f); + fclose(f); + PkgInfo *pkg_info = package_info_init(); + package_info_parse(pkg_info, pkg_info_str); + + TEST_CHECK(!strcmp(pkg_info->name, "xcursor-dmz")); + TEST_CHECK(!strcmp(pkg_info->base, "xcursor-dmz")); + TEST_CHECK(!strcmp(pkg_info->version, "0.4.5-2")); + TEST_CHECK(!strcmp(pkg_info->description, "Style neutral, scalable cursor theme")); + TEST_CHECK(!strcmp(pkg_info->url, "https://packages.debian.org/sid/dmz-cursor-theme")); + TEST_CHECK(pkg_info->build_date == 1673751613); + TEST_CHECK(!strcmp(pkg_info->packager, "Unknown Packager")); + TEST_CHECK(pkg_info->size == 3469584); + TEST_CHECK(!strcmp(pkg_info->arch, "any")); + + TEST_CHECK(!strcmp(pkg_info->licenses->array[0], "MIT")); + TEST_CHECK(!strcmp(pkg_info->replaces->array[0], "test1")); + TEST_CHECK(!strcmp(pkg_info->groups->array[0], "x11")); + TEST_CHECK(!strcmp(pkg_info->conflicts->array[0], "test2")); + TEST_CHECK(!strcmp(pkg_info->conflicts->array[1], "test3")); + TEST_CHECK(!strcmp(pkg_info->provides->array[0], "test4")); + TEST_CHECK(!strcmp(pkg_info->depends->array[0], "test5")); + TEST_CHECK(!strcmp(pkg_info->depends->array[1], "test6")); + TEST_CHECK(!strcmp(pkg_info->optdepends->array[0], "test7")); + TEST_CHECK(!strcmp(pkg_info->makedepends->array[0], "xorg-xcursorgen")); + TEST_CHECK(!strcmp(pkg_info->checkdepends->array[0], "test8")); +} + +void test_pkg_read_archive_files() { + Pkg *pkg = package_read_archive("./test/package/xcursor-dmz-0.4.5-2-any.pkg.tar.zst"); + TEST_ASSERT_(pkg != NULL, "failure parsing pkg archive"); + + FILE *f = fopen("./test/package/files", "r"); + TEST_ASSERT_(f != NULL, "could not find test files file in ./test/package"); + char buff[128]; + size_t i = 0; + + while ((fgets(buff, 128, f)) != NULL || i < pkg->files->size) { + if (buff[strlen(buff) - 1] == '\n') { + buff[strlen(buff) - 1] = '\0'; + } + + TEST_CHECK_(!strcmp(pkg->files->array[i], buff), "%s != %s", pkg->files->array[i], buff); + i++; + } + TEST_CHECK(pkg->compression = 14); + +} + +void test_pkg_read_archive_desc() { + Pkg *pkg = package_read_archive("./test/package/xcursor-dmz-0.4.5-2-any.pkg.tar.zst"); + TEST_ASSERT_(pkg != NULL, "failure parsing pkg archive"); + + char *description = package_to_description(pkg); + + FILE *f = fopen("./test/package/desc", "r"); + TEST_ASSERT_(f != NULL, "could not find test desc file in ./test/package"); + fseek(f, 0, SEEK_END); + size_t size = ftell(f); + rewind(f); + char *desc = malloc(size); + fread(desc, 1, size, f); + fclose(f); + + TEST_CHECK(!strcmp(description, desc)); +} + +TEST_LIST = { + {"pkg_info_valid_parse", test_pkg_info_parse}, + {"pkg_read_archive_files", test_pkg_read_archive_files}, + {"pkg_read_archive_desc", test_pkg_read_archive_desc}, + {NULL, NULL} +}; diff --git a/test/package/xcursor-dmz-0.4.5-2-any.pkg.tar.zst b/test/package/xcursor-dmz-0.4.5-2-any.pkg.tar.zst new file mode 100644 index 0000000..a878047 Binary files /dev/null and b/test/package/xcursor-dmz-0.4.5-2-any.pkg.tar.zst differ