diff options
| -rw-r--r-- | .gitignore | 79 | ||||
| -rw-r--r-- | CMakeLists.txt | 45 | ||||
| -rw-r--r-- | LICENSE | 12 | ||||
| -rw-r--r-- | README | 2 | ||||
| -rw-r--r-- | include/bh/bh.h | 22 | ||||
| -rw-r--r-- | include/bh/hashmap.h | 237 | ||||
| -rw-r--r-- | include/bh/internal/hashmap.h | 30 | ||||
| -rw-r--r-- | include/bh/internal/queue.h | 35 | ||||
| -rw-r--r-- | include/bh/queue.h | 159 | ||||
| -rw-r--r-- | main.c | 83 | ||||
| -rwxr-xr-x | scripts/coverage.sh | 14 | ||||
| -rwxr-xr-x | scripts/trim-whitespace.sh | 2 | ||||
| -rw-r--r-- | src/hashmap.c | 343 | ||||
| -rw-r--r-- | src/queue.c | 176 | ||||
| -rw-r--r-- | tests/CMakeLists.txt | 20 | ||||
| -rw-r--r-- | tests/src/hashmap.c | 278 | ||||
| -rw-r--r-- | tests/src/queue.c | 183 | ||||
| -rw-r--r-- | unit/CMakeLists.txt | 22 | ||||
| -rw-r--r-- | unit/include/bh/unit.h | 24 | ||||
| -rw-r--r-- | unit/src/unit.c | 55 |
20 files changed, 1821 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..04ee206 --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Build directories +[Bb]uild/ +[Dd]ebug/ +[Rr]elease/ +[Bb]in/ +[Oo]bj/ +x86/ +x64/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ + +!platform/* + +# Visual Studio Code +.vscode/ + +# Doxygen +[Dd]ocs/[Hh]tml + +# Coverage +[Cc]overage diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..51d4cd1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.10) + +# Project and C standard configuration +project(bhlib LANGUAGES C) +set(CMAKE_C_STANDARD 90) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# Check for IPO/LTO +include(CheckIPOSupported) +check_ipo_supported(RESULT supported) + +if(supported) + message(STATUS "IPO/LTO enabled") + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +endif() + +# Enable testing +include(CTest) +enable_testing() + +# Set library code +set(BHLIB_SOURCE + src/hashmap.c + src/queue.c +) + +set(BHLIB_HEADER + include/bh/hashmap.h + include/bh/queue.h +) + +# Library +add_library(bhlib STATIC ${BHLIB_SOURCE} ${BHLIB_HEADER}) +target_include_directories(bhlib PUBLIC include) + +# Runtime definition +add_executable(main + main.c +) + +target_link_libraries(main bhlib) + +# Tests +add_subdirectory(unit) +add_subdirectory(tests) @@ -0,0 +1,12 @@ +Copyright (c) 2024 by Mikhail Romanko + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. @@ -0,0 +1,2 @@ +BHLib + diff --git a/include/bh/bh.h b/include/bh/bh.h new file mode 100644 index 0000000..f9df864 --- /dev/null +++ b/include/bh/bh.h @@ -0,0 +1,22 @@ +#ifndef BHLIB_H +#define BHLIB_H + +#include <stddef.h> + +#define BH_INT_TO_PTR(x) \ + ((void *)((long)(x))) + +#define BH_UINT_TO_PTR(x) \ + ((void *)((unsigned long)(x))) + +#define BH_PTR_TO_INT(x) \ + ((long)(x)) + +#define BH_PTR_TO_UINT(x) \ + ((unsigned long)(x)) + +typedef int (*bh_equal_cb_t)(const void *, const void *); +typedef size_t (*bh_hash_cb_t)(const void *); + +#endif /* BHLIB_H */ + diff --git a/include/bh/hashmap.h b/include/bh/hashmap.h new file mode 100644 index 0000000..b8c2dc5 --- /dev/null +++ b/include/bh/hashmap.h @@ -0,0 +1,237 @@ +#ifndef BHLIB_HASHMAP_H +#define BHLIB_HASHMAP_H + +#include <bh/bh.h> + +typedef struct bh_hashmap_s bh_hashmap_t; + +/** + * @brief Create new hashmap object. + * + * @param equal Function used for comparing keys + * @param hash Function used to calculate hash value of the key + * + * @return If the function succeeds, the return value is a pointer to the new + * hashmap object. + * @return If the function fails, the return value is NULL. + * + * @warning The quality of the supplied hash function will affect performance + * of the hashmap. + * + * @sa bh_hashmap_free, bh_hashmap_reserve, bh_hashmap_insert + */ +bh_hashmap_t *bh_hashmap_new(bh_equal_cb_t equal, + bh_hash_cb_t hash); + +/** + * @brief Free hashmap object. + * + * @param hashmap Valid pointer to the hashmap object. + * + * @sa bh_hashmap_clear + */ +void bh_hashmap_free(bh_hashmap_t *hashmap); + +/** + * @brief Clear the hashmap. + * + * @param hashmap Valid pointer to the hashmap object. + * + * @warning Clearing the hashmap does invalidate iterators. + * + * @sa bh_hashmap_remove + */ +void bh_hashmap_clear(bh_hashmap_t *hashmap); + +/** + * @brief Reserve space for the specified amount of elements. + * + * @param hashmap Valid pointer to the hashmap object. + * @param size The amount of elements. + * + * @return If the function succeeds, the return value is zero. + * @return If the function fails, the return value is non-zero. + * + * @warning Reserving hashmap space does invalidate iterators. + * + * @sa bh_hashmap_capacity, bh_hashmap_insert + */ +int bh_hashmap_reserve(bh_hashmap_t *hashmap, + size_t size); + +/** + * @brief Insert key/value into the hashmap. + * + * @param hashmap Valid pointer to the hashmap object. + * @param key Key + * @param value Value + * + * @return If the function succeeds, the return value is zero. + * @return If the function fails, the return value is non-zero. + * + * @warning Inserted element is owned by the caller of the function. + * @warning Inserting elements into the hashmap does invalidate iterators. + * + * @sa bbh_hashmap_remove, bh_hashmap_at + */ +int bh_hashmap_insert(bh_hashmap_t *hashmap, + void *key, + void *value); + +/** + * @brief Remove element from the hashmap. + * + * @param hashmap Valid pointer to the hashmap object. + * @param key Key. + * + * @warning Removing elements from the hashmap does invalidate iterators. + * + * @sa bh_hashmap_insert, bh_hashmap_at + */ +void bh_hashmap_remove(bh_hashmap_t *hashmap, + void *key); + +/** + * @brief Return element value by the key from the hashmap. + * + * @param hashmap Valid pointer to the hashmap. + * @param key Key. + * @param exists Pointer to the exists flag (optional). + * + * @return If the function succeeds, the return value is a valid pointer to + * the element value. + * @return If the function fails, the return value is NULL. + * + * @note If the hashmap does not contain any element with the key, the + * function will fail. + * + * @sa bh_hashmap_empty, bh_hashmap_insert + */ +void *bh_hashmap_at(bh_hashmap_t *hashmap, + void *key, + int *exists); + +/** + * @brief Check if the hashmap is empty. + * + * @param hashmap Valid pointer to the hashmap object. + * + * @return The return value is non-zero if the hashmap is empty, otherwise + * zero. + * + * @sa bh_hashmap_size + */ +int bh_hashmap_empty(bh_hashmap_t *hashmap); + +/** + * @brief Return hashmap size. + * + * @param hashmap Valid pointer to the hashmap object. + * + * @return The return value is current hashmap size. + * + * @sa bh_hashmap_empty, bh_hashmap_capacity + */ +size_t bh_hashmap_size(bh_hashmap_t *hashmap); + +/** + * @brief Return hashmap capacity. + * + * @param hashmap Valid pointer to the hashmap object. + * + * @return The return value is current hashmap capacity. + * + * @sa bh_hashmap_reserve, bh_hashmap_size + */ +size_t bh_hashmap_capacity(bh_hashmap_t *hashmap); + +/** + * @brief Return hashmap load factor. + * + * @param hashmap Valid pointer to the hashmap object. + * + * @return The return value is current hashmap load factor. + * + * @sa bh_hashmap_set_factor, bh_hashmap_capacity + */ +float bh_hashmap_factor(bh_hashmap_t *hashmap); + +/** + * @brief Set hashmap load factor. + * + * @param hashmap Valid pointer to the hashmap object. + * @param factor Load factor. + * + * @sa bh_hashmap_factor + */ +void bh_hashmap_set_factor(bh_hashmap_t *hashmap, + float factor); + +/** + * @brief Return iterator for the element by the key. + * + * @param hashmap Valid pointer to the hashmap object. + * @param key Key + * + * @return The return value is the valid iterator for the element in the + * hashmap. + * @return The return value is the NULL iterator if there is no element with + * specified key. + * + * @sa bh_hashmap_iter_key, bh_hashmap_iter_value + */ +void *bh_hashmap_iter_at(bh_hashmap_t *hashmap, + void *key); + +/** + * @brief Return iterator for the next element in the hashmap. + * + * @param hashmap Valid pointer to the hashmap object. + * @param iter Valid or NULL iterator. + * + * @return The return value is the valid iterator for the next element in the + * hashmap. + * @return The return value is the NULL iterator if there is no more elements + * in the hashmap. + * + * @sa bh_hashmap_iter_key, bh_hashmap_iter_value + */ +void *bh_hashmap_iter_next(bh_hashmap_t *hashmap, + void *iter); + +/** + * @brief Remove element from the hashmap by the iterator. + * + * @param hashmap Valid pointer to the hashmap object. + * @param key Valid iterator. + * + * @warning Removing elements from the hashmap does invalidate iterators. + * + * @sa bh_hashmap_insert, bh_hashmap_at + */ +void bh_hashmap_iter_remove(bh_hashmap_t *hashmap, + void *iter); + +/** + * @brief Return pointer to the element's key. + * + * @param iter Valid iterator. + * + * @return The return value is the stored element key. + * + * @sa bh_hashmap_iter_value, bh_hashmap_iter_next + */ +void *bh_hashmap_iter_key(void *iter); + +/** + * @brief Return pointer to the element's value. + * + * @param iter Valid iterator. + * + * @return The return value is the stored element value. + * + * @sa bh_hashmap_iter_key, bh_hashmap_iter_next + */ +void *bh_hashmap_iter_value(void *iter); + +#endif /* BHLIB_HASHMAP_H */ diff --git a/include/bh/internal/hashmap.h b/include/bh/internal/hashmap.h new file mode 100644 index 0000000..8d62c16 --- /dev/null +++ b/include/bh/internal/hashmap.h @@ -0,0 +1,30 @@ +#ifndef BHLIB_INTERNAL_HASHMAP_H +#define BHLIB_INTERNAL_HASHMAP_H + +#include <bh/hashmap.h> + +typedef struct bh_hashmap_node_s +{ + void *key; + void *value; +} bh_hashmap_node_t; + +struct bh_hashmap_s +{ + bh_hashmap_node_t *data; + size_t *psls; + size_t size; + size_t capacity; + size_t threshold; + bh_equal_cb_t equal; + bh_hash_cb_t hash; + float factor; +}; + +void bh_hashmap_init(bh_hashmap_t *hashmap, + bh_equal_cb_t equal, + bh_hash_cb_t hash); + +void bh_hashmap_destroy(bh_hashmap_t *hashmap); + +#endif /* BHLIB_INTERNAL_HASHMAP_H */ diff --git a/include/bh/internal/queue.h b/include/bh/internal/queue.h new file mode 100644 index 0000000..13a6cad --- /dev/null +++ b/include/bh/internal/queue.h @@ -0,0 +1,35 @@ +#ifndef BHLIB_INTERNAL_QUEUE_H +#define BHLIB_INTERNAL_QUEUE_H + +#include <bh/queue.h> + +struct bh_queue_s +{ + void **data; + size_t size; + size_t head; + size_t tail; + size_t capacity; +}; + +/** + * @internal + * @brief Initialize embedded queue object. + * + * @param queue Valid pointer to the queue object. + * + * @sa bh_queue_destroy + */ +void bh_queue_init(bh_queue_t *queue); + +/** + * @internal + * @brief Destroy embedded queue object. + * + * @param queue Valid pointer to the queue object. + * + * @sa bh_queue_init + */ +void bh_queue_destroy(bh_queue_t *queue); + +#endif /* BHLIB_INTERNAL_QUEUE_H */ diff --git a/include/bh/queue.h b/include/bh/queue.h new file mode 100644 index 0000000..c5185ef --- /dev/null +++ b/include/bh/queue.h @@ -0,0 +1,159 @@ +#ifndef BH_QUEUE_H +#define BH_QUEUE_H + +#include <bh/bh.h> + +typedef struct bh_queue_s bh_queue_t; + +/** + * @brief Create new queue object. + * + * @return If the function succeeds, the return value is a pointer to the new + * queue object. + * @return If the function fails, the return value is NULL. + * + * @sa bh_queue_free, bh_queue_reserve, bh_queue_insert + */ +bh_queue_t *bh_queue_new(void); + +/** + * @brief Free queue object. + * + * @param queue Valid pointer to the queue object. + * + * @sa bh_queue_clear + */ +void bh_queue_free(bh_queue_t *queue); + +/** + * @brief Clear the queue. + * + * @param queue Valid pointer to the queue object. + * + * @warning Clearing the queue does invalidate iterators. + * + * @sa bh_queue_remove + */ +void bh_queue_clear(bh_queue_t *queue); + +/** + * @brief Reserve space for the specified amount of elements. + * + * @param queue Valid pointer to the queue object. + * @param size The amount of elements. + * + * @return If the function succeeds, the return value is zero. + * @return If the function fails, the return value is non-zero. + * + * @warning Reserving queue space does invalidate iterators. + * + * @sa bh_queue_capacity, bh_queue_insert + */ +int bh_queue_reserve(bh_queue_t *queue, + size_t size); + +/** + * @brief Insert element at the end of the queue. + * + * @param queue Valid pointer to the queue object. + * @param value Element. + * + * @return If the function succeeds, the return value is zero. + * @return If the function fails, the return value is non-zero. + * + * @warning Inserted element is owned by the caller of the function. + * @warning Inserting elements into the queue does invalidate iterators. + * + * @sa bh_queue_remove, bh_queue_front + */ +int bh_queue_insert(bh_queue_t *queue, + void *value); + +/** + * @brief Remove element from the front of the queue. + * + * @param queue Valid pointer to the queue object. + * + * @warning Removing elements from the queue does invalidate iterators. + * + * @sa bh_queue_insert, bh_queue_front + */ +void bh_queue_remove(bh_queue_t *queue); + +/** + * @brief Return element from the front of the queue. + * + * @param queue Valid pointer to the queue object. + * + * @return If the function succeeds, the return value is a valid pointer to + * the element. + * @return If the function fails, the return value is NULL. + * + * @note If the queue is empty, function will fail to return element from + * the queue. + * + * @sa bh_queue_empty, bh_queue_insert + */ +void *bh_queue_front(bh_queue_t *queue); + +/** + * @brief Check if the queue is empty. + * + * @param queue Valid pointer to the queue object. + * + * @return The return value is non-zero if the queue is empty, otherwise zero. + * + * @sa bh_queue_size + */ +int bh_queue_empty(bh_queue_t *queue); + +/** + * @brief Return queue size. + * + * @param queue Valid pointer to the queue object. + * + * @return The return value is current queue size. + * + * @sa bh_queue_empty, bh_queue_capacity + */ +size_t bh_queue_size(bh_queue_t *queue); + +/** + * @brief Return queue capacity. + * + * @param queue Valid pointer to the queue object. + * + * @return The return value is current queue capacity. + * + * @sa bh_queue_reserve, bh_queue_size + */ +size_t bh_queue_capacity(bh_queue_t *queue); + +/** + * @brief Return iterator for the next element in the queue. + * + * @param queue Valid pointer to the queue object. + * @param iter Valid or NULL iterator. + * + * @return The return value is the valid iterator for the next element in the + * queue. + * @return The return value is the NULL iterator if there is no more elements + * in the queue. + * + * @sa bh_queue_iter_value + */ +void *bh_queue_iter_next(bh_queue_t *queue, + void *iter); + +/** + * @brief Return pointer to the element's value. + * + * @param iter Valid iterator. + * + * @return The return value is the stored element. + * + * @sa bh_queue_iter_next + */ +void *bh_queue_iter_value(void *iter); + +#endif /* BH_QUEUE_H */ @@ -0,0 +1,83 @@ +#include <bh/queue.h> +#include <bh/hashmap.h> +#include <stdio.h> +#include <stdint.h> + +#define BH_INT_TO_PTR(x) \ + ((void *)((long)(x))) + +#define BH_UINT_TO_PTR(x) \ + ((void *)((unsigned long)(x))) + +#define BH_PTR_TO_INT(x) \ + ((long)(x)) + +#define BH_PTR_TO_UINT(x) \ + ((unsigned long)(x)) + +size_t ptr_hash(const void *item) +{ + return BH_PTR_TO_INT(item); +} + +int ptr_equal(const void *lhs, const void *rhs) +{ + return BH_PTR_TO_INT(lhs) - BH_PTR_TO_INT(rhs); +} + +void foo() +{ + bh_hashmap_t *hashmap; + size_t i; + void *iter; + + hashmap = bh_hashmap_new((bh_equal_cb_t)ptr_equal, (bh_hash_cb_t)ptr_hash); + + for (i = 0; i < 16; i++) + bh_hashmap_insert(hashmap, (void*)i, (void*)(i * 4)); + + iter = bh_hashmap_iter_next(hashmap, NULL); + while (iter) + { + printf("%zu: %zu\n", BH_PTR_TO_INT(bh_hashmap_iter_key(iter)), BH_PTR_TO_INT(bh_hashmap_iter_value(iter))); + iter = bh_hashmap_iter_next(hashmap, iter); + } + + bh_hashmap_free(hashmap); +} + +int main() +{ + bh_queue_t *queue; + void *iter; + size_t i, j; + + foo(); + + queue = bh_queue_new(); + + for (j = 0; j < 32; j++) + { + printf("%zu %zu\n", bh_queue_size(queue), bh_queue_capacity(queue)); + + for (i = 0; i < 4; i++) + bh_queue_insert(queue, (void *)(j * 4 + i)); + + printf("%zu %zu\n", bh_queue_size(queue), bh_queue_capacity(queue)); + + for (i = 0; i < 2; i++) + bh_queue_remove(queue); + } + + printf("%zu %zu\n", bh_queue_size(queue), bh_queue_capacity(queue)); + + iter = bh_queue_iter_next(queue, NULL); + while (iter) + { + printf("%d\n", (int)bh_queue_iter_value(iter)); + iter = bh_queue_iter_next(queue, iter); + } + + bh_queue_free(queue); + return 0; +} diff --git a/scripts/coverage.sh b/scripts/coverage.sh new file mode 100755 index 0000000..9597cf6 --- /dev/null +++ b/scripts/coverage.sh @@ -0,0 +1,14 @@ +#!/bin/sh +ulimit -Sv 320000 +rm -r build coverage +mkdir build coverage +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS_DEBUG="-g -fprofile-arcs -ftest-coverage" +cmake --build build +cd build +#ctest -T Test -T Coverage +ctest +cd .. +#echo Report > coverage.txt +#find . -iname "*.gcda" -exec gcov {} \; +#find . -iname "*.gcno" -exec echo {} >> coverage.txt \; -exec gcov -t {} >> coverage.txt \; +gcovr --html-details coverage/index.html diff --git a/scripts/trim-whitespace.sh b/scripts/trim-whitespace.sh new file mode 100755 index 0000000..34a9c58 --- /dev/null +++ b/scripts/trim-whitespace.sh @@ -0,0 +1,2 @@ +#!/bin/sh +find . \( -iname "*.h" -o -iname "*.c" \) -exec sed -i "s/[ \t]*$//" {} \;
\ No newline at end of file diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100644 index 0000000..c9fb315 --- /dev/null +++ b/src/hashmap.c @@ -0,0 +1,343 @@ +#include <bh/internal/hashmap.h> +#include <stdlib.h> +#include <string.h> + +bh_hashmap_t *bh_hashmap_new(bh_equal_cb_t equal, + bh_hash_cb_t hash) +{ + bh_hashmap_t *result; + + result = malloc(sizeof(*result)); + if (result) + bh_hashmap_init(result, equal, hash); + + return result; +} + +void bh_hashmap_free(bh_hashmap_t *hashmap) +{ + bh_hashmap_destroy(hashmap); + free(hashmap); +} + +void bh_hashmap_init(bh_hashmap_t *hashmap, + bh_equal_cb_t equal, + bh_hash_cb_t hash) +{ + memset(hashmap, 0, sizeof(*hashmap)); + hashmap->factor = 0.75f; + hashmap->equal = equal; + hashmap->hash = hash; +} + +void bh_hashmap_destroy(bh_hashmap_t *hashmap) +{ + if (hashmap->capacity) + { + free(hashmap->data); + free(hashmap->psls); + } +} + +void bh_hashmap_clear(bh_hashmap_t *hashmap) +{ + if (hashmap->capacity) + memset(hashmap->psls, 0, hashmap->capacity * sizeof(size_t)); + hashmap->size = 0; +} + +int bh_hashmap_reserve(bh_hashmap_t *hashmap, + size_t size) +{ + bh_hashmap_t other; + size_t capacity, max_capacity, threshold; + + /* Calculate hashmap max capacity and capacity threshold */ + capacity = hashmap->capacity; + threshold = hashmap->capacity * hashmap->factor; + max_capacity = ((size_t)-1) / sizeof(bh_hashmap_node_t); + + /* New capacity can't be smaller then current hashmap size */ + if (size < hashmap->size) + size = hashmap->size; + + /* Find new hashmap capacity */ + if (!size) + { + /* No capacity needed */ + capacity = 0; + threshold = 0; + } + else if (size > threshold) + { + /* Bigger capacity needed - grow the hashmap */ + while (size > threshold) + { + capacity = (capacity) ? (capacity * 2) : (16); + threshold = capacity * hashmap->factor; + + /* Capacity can't be bigger than max capacity and overflow */ + if (capacity > max_capacity || capacity < 16) + return -1; + } + } + else + { + /* Smaller capacity needed - shrink the hashmap */ + while (size <= threshold / 2 && capacity > 16) + { + capacity /= 2; + threshold = capacity * hashmap->factor; + } + } + + /* Prevent same size reallocation */ + if (capacity == hashmap->capacity) + return 0; + + /* Initialize new hashmap */ + bh_hashmap_init(&other, hashmap->equal, hashmap->hash); + other.factor = hashmap->factor; + + if (capacity) + { + void *iter; + + /* Allocate new memory for the hashmap */ + other.data = malloc(sizeof(*other.data) * capacity); + + other.psls = malloc(sizeof(size_t) * capacity); + other.threshold = threshold; + other.capacity = capacity; + + /* Check for allocations failure */ + if (!other.data || !other.psls) + { + if (other.data) + free(other.data); + if (other.psls) + free(other.psls); + return -1; + } + + /* Reset probe sequence lengths */ + memset(other.psls, 0, sizeof(size_t) * other.capacity); + + /* Iterate and insert data into hashmap */ + iter = bh_hashmap_iter_next(hashmap, NULL); + while (iter) + { + void *key, *value; + + key = bh_hashmap_iter_key(iter); + value = bh_hashmap_iter_value(iter); + bh_hashmap_insert(&other, key, value); + + iter = bh_hashmap_iter_next(hashmap, iter); + } + } + + /* Swap hashmaps */ + bh_hashmap_destroy(hashmap); + *hashmap = other; + return 0; +} + +int bh_hashmap_insert(bh_hashmap_t *hashmap, + void *key, + void *value) +{ + size_t bucket, psl, tmp_psl; + bh_hashmap_node_t item, tmp; + + /* Try to stay below hashmap threshold */ + if (hashmap->size + 1 > hashmap->threshold) + if (bh_hashmap_reserve(hashmap, hashmap->size + 1)) + if (hashmap->size >= hashmap->capacity) + return -1; + + /* Prepare inserted data and set PSL to 1 */ + item.key = key; + item.value = value; + psl = 1; + + /* Calculate prefered bucket index */ + bucket = hashmap->hash(key) & (hashmap->capacity - 1); + + /* Find empty bucket */ + while (hashmap->psls[bucket]) + { + /* Current bucket is richer then us - swap elements */ + if (psl > hashmap->psls[bucket]) + { + tmp = hashmap->data[bucket]; + tmp_psl = hashmap->psls[bucket]; + hashmap->data[bucket] = item; + hashmap->psls[bucket] = psl; + item = tmp; + psl = tmp_psl; + } + + bucket = (bucket + 1) & (hashmap->capacity - 1); + psl++; + } + + /* Found empty bucket - place item here */ + hashmap->data[bucket] = item; + hashmap->psls[bucket] = psl; + hashmap->size++; + + return 0; +} + +void bh_hashmap_remove(bh_hashmap_t *hashmap, + void *key) +{ + void *iter; + + iter = bh_hashmap_iter_at(hashmap, key); + if (iter) + bh_hashmap_iter_remove(hashmap, iter); +} + +void *bh_hashmap_at(bh_hashmap_t *hashmap, + void *key, + int *exists) +{ + void *iter; + + iter = bh_hashmap_iter_at(hashmap, key); + if (iter) + { + /* If exists flag passed - set to 1 */ + if (exists) + *exists = 1; + return bh_hashmap_iter_value(iter); + } + + /* If exists flag passed - set to 0 */ + if (exists) + *exists = 0; + return NULL; +} + +int bh_hashmap_empty(bh_hashmap_t *hashmap) +{ + return !hashmap->size; +} + +size_t bh_hashmap_size(bh_hashmap_t *hashmap) +{ + return hashmap->size; +} + +size_t bh_hashmap_capacity(bh_hashmap_t *hashmap) +{ + return hashmap->capacity; +} + +float bh_hashmap_factor(bh_hashmap_t *hashmap) +{ + return hashmap->factor; +} + +void bh_hashmap_set_factor(bh_hashmap_t *hashmap, + float factor) +{ + factor = (factor > 1.0f) ? (1.0f) : (factor); + factor = (factor < 0.15f) ? (0.15f) : (factor); + + hashmap->factor = factor; + hashmap->threshold = hashmap->capacity * factor; +} + +void *bh_hashmap_iter_at(bh_hashmap_t *hashmap, + void *key) +{ + size_t bucket, psl; + + /* Nothing can be in empty map */ + if (!hashmap->size) + return NULL; + + /* Calculate prefered bucket index and set PSL to 1 */ + bucket = hashmap->hash(key) & (hashmap->capacity - 1); + psl = 1; + + /* Iterate hashmap until we find element or find richer bucket */ + while (hashmap->psls[bucket] >= psl && hashmap->equal(hashmap->data[bucket].key, key)) + { + bucket = (bucket + 1) & (hashmap->capacity - 1); + psl++; + } + + /* If bucket is poorer or equal to us - we found our element */ + if (hashmap->psls[bucket] >= psl) + return hashmap->data + bucket; + + return NULL; +} + +void *bh_hashmap_iter_next(bh_hashmap_t *hashmap, + void *iter) +{ + bh_hashmap_node_t *item; + + item = (bh_hashmap_node_t *)iter; + while (1) + { + /* Advance or set iterator to the first element */ + if (item) + item++; + else + item = hashmap->data; + + /* Check iterator for validity */ + if (item >= hashmap->data + hashmap->capacity) + return NULL; + + /* Check iterator's item PSL */ + if (hashmap->psls[item - hashmap->data]) + return item; + } +} + +void bh_hashmap_iter_remove(bh_hashmap_t *hashmap, + void *iter) +{ + size_t bucket, next_bucket; + + /* Check if hashmap is empty or we are given NULL iterator */ + if (!iter || !hashmap->size) + return; + + /* Adjust hashmap size, calculate current and next bucket index */ + hashmap->size--; + bucket = (bh_hashmap_node_t *)iter - hashmap->data; + next_bucket = (bucket + 1) & (hashmap->capacity - 1); + + /* Shift all elements toward their preffered place */ + while (hashmap->psls[next_bucket] > 1) + { + /* Copy item and adjust PSL */ + hashmap->data[bucket] = hashmap->data[next_bucket]; + hashmap->psls[bucket] = hashmap->psls[next_bucket] - 1; + + /* Calculate next bucket index */ + bucket = next_bucket; + next_bucket = (bucket + 1) & (hashmap->capacity - 1); + } + + /* Reset bucket's PSL (mark empty) */ + hashmap->psls[bucket] = 0; +} + +void *bh_hashmap_iter_key(void *iter) +{ + return ((bh_hashmap_node_t *)iter)->key; +} + +void *bh_hashmap_iter_value(void *iter) +{ + return ((bh_hashmap_node_t *)iter)->value; +} diff --git a/src/queue.c b/src/queue.c new file mode 100644 index 0000000..8d48401 --- /dev/null +++ b/src/queue.c @@ -0,0 +1,176 @@ +#include <bh/internal/queue.h> +#include <stdlib.h> +#include <string.h> + +bh_queue_t *bh_queue_new(void) +{ + bh_queue_t *result; + + result = malloc(sizeof(*result)); + if (result) + bh_queue_init(result); + + return result; +} + +void bh_queue_free(bh_queue_t *queue) +{ + bh_queue_destroy(queue); + free(queue); +} + +void bh_queue_init(bh_queue_t *queue) +{ + memset(queue, 0, sizeof(*queue)); +} + +void bh_queue_destroy(bh_queue_t *queue) +{ + if (queue->capacity) + free(queue->data); +} + +void bh_queue_clear(bh_queue_t *queue) +{ + queue->head = 0; + queue->tail = 0; + queue->size = 0; +} + +int bh_queue_reserve(bh_queue_t *queue, + size_t size) +{ + bh_queue_t other; + + /* New capacity should be great or equal to current size */ + if (size < queue->size) + size = queue->size; + + /* New capacity should not exceed maximum capacity */ + if (size > ((size_t)-1) / sizeof(void *)) + return -1; + + /* Prevent same size memory reallocation */ + if (size == queue->capacity) + return 0; + + /* Prepare new empty queue */ + bh_queue_init(&other); + if (size) + { + void *iter; + + /* Allocate new capacity for the queue */ + other.data = malloc(size * sizeof(void *)); + other.capacity = size; + if (!other.data) + return -1; + + /* Iterate over old queue and insert data into new queue */ + iter = bh_queue_iter_next(queue, NULL); + while (iter) + { + bh_queue_insert(&other, bh_queue_iter_value(iter)); + iter = bh_queue_iter_next(queue, iter); + } + } + + /* If old queue had allocated data - free it */ + if (queue->capacity) + free(queue->data); + + /* Copy queue information */ + *queue = other; + return 0; +} + +int bh_queue_insert(bh_queue_t *queue, + void *value) +{ + /* Check if queue can contain new element */ + if (queue->size + 1 > queue->capacity) + { + size_t capacity; + + /* Check potential size overflow and reserve capacity */ + capacity = (queue->capacity) ? (queue->capacity * 2) : (16); + if (capacity < queue->capacity || bh_queue_reserve(queue, capacity)) + return -1; + } + + /* Increase queue size and advance tail index */ + queue->data[queue->tail] = value; + queue->size++; + if (++queue->tail >= queue->capacity) + queue->tail = 0; + + return 0; +} + +void bh_queue_remove(bh_queue_t *queue) +{ + /* Do nothing if queue is empty */ + if (!queue->size) + return; + + /* Decrease queue size and advance head index */ + queue->size--; + if (++queue->head >= queue->capacity) + queue->head = 0; +} + +void *bh_queue_front(bh_queue_t *queue) +{ + /* Do nothing if queue is empty */ + if (!queue->size) + return NULL; + + /* Return front element */ + return queue->data[queue->head]; +} + +int bh_queue_empty(bh_queue_t *queue) +{ + return !queue->size; +} + +size_t bh_queue_size(bh_queue_t *queue) +{ + return queue->size; +} + +size_t bh_queue_capacity(bh_queue_t *queue) +{ + return queue->capacity; +} + +void *bh_queue_iter_next(bh_queue_t *queue, + void *iter) +{ + void **element = (void **)iter; + + /* Do nothing if queue is empty */ + if (!queue->size) + return NULL; + + /* Advance or set iterator */ + if (element) + { + element++; + if (element == queue->data + queue->capacity) + element = queue->data; + + /* Check if we reached the end */ + if (element == queue->data + queue->tail) + return NULL; + } + else + element = queue->data + queue->head; + + return element; +} + +void *bh_queue_iter_value(void *iter) +{ + return *(void **)iter; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..e111e33 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,20 @@ +set(CMAKE_C_STANDARD 90) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +# Enable warnings and pedantics +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic") + +# Enable testing +include(CTest) +enable_testing() + +# Search files +file(GLOB TEST_FILES "src/*.c") + +foreach(TEST_FILENAME ${TEST_FILES}) + get_filename_component(TEST_NAME ${TEST_FILENAME} NAME_WE) + add_executable("${TEST_NAME}" ${TEST_FILENAME}) + target_link_libraries("${TEST_NAME}" bhlib bhunit) + add_test(NAME "${TEST_NAME}" COMMAND "${TEST_NAME}") +endforeach() diff --git a/tests/src/hashmap.c b/tests/src/hashmap.c new file mode 100644 index 0000000..7fda5fc --- /dev/null +++ b/tests/src/hashmap.c @@ -0,0 +1,278 @@ +#include <bh/internal/hashmap.h> +#include <bh/unit.h> + +static size_t direct_hash(const void *ptr) +{ + return BH_PTR_TO_INT(ptr); +} + +static int direct_equal(const void *lhs, const void *rhs) +{ + return BH_PTR_TO_INT(lhs) - BH_PTR_TO_INT(rhs); +} + +static int init_destroy(void) +{ + bh_hashmap_t hashmap; + + bh_hashmap_init(&hashmap, direct_equal, direct_hash); + + /* Check internal state is valid and empty */ + bh_unit_assert(hashmap.data == NULL); + bh_unit_assert(hashmap.psls == NULL); + bh_unit_assert(hashmap.size == 0); + bh_unit_assert(hashmap.capacity == 0); + bh_unit_assert(hashmap.threshold == 0); + bh_unit_assert(hashmap.factor >= 0.15f && hashmap.factor <= 1.0f); + bh_unit_assert(hashmap.equal == direct_equal); + bh_unit_assert(hashmap.hash == direct_hash); + + bh_hashmap_destroy(&hashmap); + return 0; +} + +static int new_free(void) +{ + bh_hashmap_t *hashmap; + + hashmap = bh_hashmap_new(direct_equal, direct_hash); + bh_unit_assert(hashmap != NULL); + + /* Check internal state is valid and empty */ + bh_unit_assert(hashmap->data == NULL); + bh_unit_assert(hashmap->psls == NULL); + bh_unit_assert(hashmap->size == 0); + bh_unit_assert(hashmap->capacity == 0); + bh_unit_assert(hashmap->threshold == 0); + bh_unit_assert(hashmap->factor >= 0.15f && hashmap->factor <= 1.0f); + bh_unit_assert(hashmap->equal == direct_equal); + bh_unit_assert(hashmap->hash == direct_hash); + + bh_hashmap_free(hashmap); + return 0; +} + +static int grow_shrink(void) +{ + bh_hashmap_t *hashmap; + bh_hashmap_node_t *old_data; + size_t *old_psls; + void *iter; + + hashmap = bh_hashmap_new(direct_equal, direct_hash); + bh_unit_assert(hashmap != NULL); + + /* Allocate space for 1024 entries and insert 1 element */ + bh_unit_assert(bh_hashmap_reserve(hashmap, 1024) == 0); + bh_unit_assert(bh_hashmap_insert(hashmap, BH_INT_TO_PTR(1337), BH_INT_TO_PTR(80085)) == 0); + + /* Check hashmap contents */ + iter = bh_hashmap_iter_next(hashmap, NULL); + bh_unit_assert(iter != NULL); + bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_key(iter)) == 1337); + bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_value(iter)) == 80085); + bh_unit_assert(bh_hashmap_iter_next(hashmap, iter) == NULL); + + bh_unit_assert(hashmap->data != NULL); + bh_unit_assert(hashmap->psls != NULL); + bh_unit_assert(hashmap->capacity >= 1024); + bh_unit_assert(hashmap->threshold == (size_t)(hashmap->capacity * hashmap->factor)); + bh_unit_assert(hashmap->size == 1); + + /* Save old pointers */ + old_data = hashmap->data; + old_psls = hashmap->psls; + + /* Change factor and grow */ + bh_hashmap_set_factor(hashmap, 0.35f); + bh_unit_assert(bh_hashmap_reserve(hashmap, 8192) == 0); + + bh_unit_assert(hashmap->data != NULL); + bh_unit_assert(hashmap->psls != NULL); + bh_unit_assert(hashmap->capacity >= 8192); + bh_unit_assert(hashmap->data != old_data); + bh_unit_assert(hashmap->psls != old_psls); + bh_unit_assert(hashmap->factor == 0.35f); + bh_unit_assert(hashmap->threshold == (size_t)(hashmap->capacity * hashmap->factor)); + bh_unit_assert(hashmap->size == 1); + + old_data = hashmap->data; + old_psls = hashmap->psls; + + /* Check hashmap contents */ + iter = bh_hashmap_iter_next(hashmap, NULL); + bh_unit_assert(iter != NULL); + bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_key(iter)) == 1337); + bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_value(iter)) == 80085); + bh_unit_assert(bh_hashmap_iter_next(hashmap, iter) == NULL); + + /* Shrink */ + bh_unit_assert(bh_hashmap_reserve(hashmap, 0) == 0); + + bh_unit_assert(hashmap->data != NULL); + bh_unit_assert(hashmap->psls != NULL); + bh_unit_assert(hashmap->capacity < 8192); + bh_unit_assert(hashmap->data != old_data); + bh_unit_assert(hashmap->psls != old_psls); + bh_unit_assert(hashmap->factor == 0.35f); + bh_unit_assert(hashmap->threshold == (size_t)(hashmap->capacity * hashmap->factor)); + bh_unit_assert(hashmap->size == 1); + + /* Check hashmap contents */ + iter = bh_hashmap_iter_next(hashmap, NULL); + bh_unit_assert(iter != NULL); + bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_key(iter)) == 1337); + bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_value(iter)) == 80085); + bh_unit_assert(bh_hashmap_iter_next(hashmap, iter) == NULL); + + /* Shrink to 0 (deallocate) */ + bh_hashmap_clear(hashmap); + bh_unit_assert(hashmap->size == 0); + + bh_unit_assert(bh_hashmap_reserve(hashmap, 0) == 0); + bh_unit_assert(hashmap->capacity == 0); + bh_unit_assert(hashmap->size == 0); + + /* Check hashmap contents */ + iter = bh_hashmap_iter_next(hashmap, NULL); + bh_unit_assert(iter == NULL); + + bh_hashmap_free(hashmap); + return 0; +} + +static int insert_remove(void) +{ + bh_hashmap_t *hashmap; + size_t i, added, removed; + void *iter; + + hashmap = bh_hashmap_new(direct_equal, direct_hash); + bh_unit_assert(hashmap != NULL); + bh_hashmap_set_factor(hashmap, 1.0f); + + /* Insert elements into hashmap */ + added = 0; + for (i = 1024; i > 0; i--) + { + added += (i - 1) / 4; + bh_unit_assert(bh_hashmap_insert(hashmap, BH_INT_TO_PTR((i - 1) / 4), BH_INT_TO_PTR(i)) == 0); + } + + /* Remove elements */ + iter = bh_hashmap_iter_next(hashmap, NULL); + removed = 0; + while (iter) + { + removed += BH_PTR_TO_INT(bh_hashmap_iter_key(iter)); + bh_hashmap_iter_remove(hashmap, iter); + + iter = bh_hashmap_iter_next(hashmap, NULL); + } + + /* Check inserted elements are equal to removed */ + bh_unit_assert(added == removed); + + bh_hashmap_free(hashmap); + return 0; +} + +static int lookup(void) +{ + bh_hashmap_t *hashmap; + size_t i; + + hashmap = bh_hashmap_new(direct_equal, direct_hash); + bh_unit_assert(hashmap != NULL); + + /* Insert elements into hashmap */ + for (i = 0; i < 256; i++) + bh_unit_assert(bh_hashmap_insert(hashmap, BH_INT_TO_PTR(i * 4), BH_INT_TO_PTR(i)) == 0); + + /* Lookup inserted elements */ + for (i = 0; i < 256; i++) + { + int exists; + + bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_at(hashmap, BH_INT_TO_PTR(i * 4), NULL)) == i); + bh_hashmap_at(hashmap, BH_INT_TO_PTR(i * 4), &exists); + bh_unit_assert(exists == 1); + } + + /* Lookup non-existing elements */ + for (i = 256; i < 512; i++) + { + int exists; + + bh_unit_assert(bh_hashmap_at(hashmap, BH_INT_TO_PTR(i * 4), NULL) == NULL); + bh_hashmap_at(hashmap, BH_INT_TO_PTR(i * 4), &exists); + bh_unit_assert(exists == 0); + } + + bh_hashmap_free(hashmap); + return 0; +} + +static int clear(void) +{ + bh_hashmap_t *hashmap; + size_t i; + + hashmap = bh_hashmap_new(direct_equal, direct_hash); + bh_unit_assert(hashmap != NULL); + + /* Insert elements into hashmap */ + for (i = 0; i < 128; i++) + bh_unit_assert(bh_hashmap_insert(hashmap, BH_INT_TO_PTR(i), 0) == 0); + + bh_hashmap_clear(hashmap); + + /* Remove non-existing elements */ + for (i = 0; i < 128; i++) + bh_hashmap_remove(hashmap, BH_INT_TO_PTR(i)); + + bh_hashmap_free(hashmap); + return 0; +} + +static int fields(void) +{ + bh_hashmap_t *hashmap; + size_t i; + + hashmap = bh_hashmap_new(direct_equal, direct_hash); + bh_unit_assert(hashmap != NULL); + bh_unit_assert(bh_hashmap_empty(hashmap) == 1); + + /* Insert elements into hashmap */ + for (i = 0; i < 14; i++) + bh_unit_assert(bh_hashmap_insert(hashmap, BH_INT_TO_PTR(i), NULL) == 0); + + /* Check hashmap fields correspond to getter functions */ + bh_unit_assert(hashmap->size == 14); + bh_unit_assert(hashmap->capacity >= 14); + bh_unit_assert(bh_hashmap_size(hashmap) == hashmap->size); + bh_unit_assert(bh_hashmap_capacity(hashmap) == hashmap->capacity); + bh_unit_assert(bh_hashmap_factor(hashmap) == hashmap->factor); + bh_unit_assert(bh_hashmap_empty(hashmap) == 0); + + bh_hashmap_free(hashmap); + return 0; +} + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + + /* Add unit tests */ + bh_unit_add("init_destroy", init_destroy); + bh_unit_add("new_free", new_free); + bh_unit_add("grow_shrink", grow_shrink); + bh_unit_add("insert_remove", insert_remove); + bh_unit_add("lookup", lookup); + bh_unit_add("clear", clear); + bh_unit_add("fields", fields); + + return bh_unit_run(); +} diff --git a/tests/src/queue.c b/tests/src/queue.c new file mode 100644 index 0000000..8c805a6 --- /dev/null +++ b/tests/src/queue.c @@ -0,0 +1,183 @@ +#include <bh/internal/queue.h> +#include <bh/unit.h> + +static int init_destroy(void) +{ + bh_queue_t queue; + + bh_queue_init(&queue); + + bh_unit_assert(queue.size == 0); + bh_unit_assert(queue.capacity == 0); + bh_unit_assert(queue.data == NULL); + bh_unit_assert(queue.head == queue.tail); + + bh_queue_destroy(&queue); + + return 0; +} + +static int new_free(void) +{ + bh_queue_t *queue; + + queue = bh_queue_new(); + bh_unit_assert(queue != NULL); + bh_unit_assert(queue->size == 0); + bh_unit_assert(queue->capacity == 0); + bh_unit_assert(queue->data == NULL); + bh_unit_assert(queue->head == queue->tail); + + bh_queue_free(queue); + return 0; +} + +static int grow_shrink(void) +{ + bh_queue_t *queue; + void *old_data; + + queue = bh_queue_new(); + bh_unit_assert(queue != NULL); + + /* Reserve 1024 elements and insert item into queue */ + bh_unit_assert(bh_queue_reserve(queue, 1024) == 0); + bh_unit_assert(bh_queue_insert(queue, BH_INT_TO_PTR(1337)) == 0); + bh_unit_assert(queue->capacity >= 1024); + bh_unit_assert(queue->size == 1); + bh_unit_assert(bh_queue_empty(queue) == 0); + bh_unit_assert(queue->data != NULL); + + /* Check queue content */ + bh_unit_assert(BH_PTR_TO_INT(bh_queue_front(queue)) == 1337); + + old_data = queue->data; + + /* Grow queue */ + bh_unit_assert(bh_queue_reserve(queue, 8192) == 0); + bh_unit_assert(queue->capacity >= 8192); + bh_unit_assert(queue->size == 1); + bh_unit_assert(bh_queue_empty(queue) == 0); + bh_unit_assert(queue->data != NULL); + bh_unit_assert(queue->data != old_data); + + /* Check queue content */ + bh_unit_assert(BH_PTR_TO_INT(bh_queue_front(queue)) == 1337); + + old_data = queue->data; + + /* Shrink the queue */ + bh_unit_assert(bh_queue_reserve(queue, 0) == 0); + bh_unit_assert(queue->capacity >= 1 && queue->capacity <= 8192); + bh_unit_assert(queue->size == 1); + bh_unit_assert(bh_queue_empty(queue) == 0); + bh_unit_assert(queue->data != NULL); + bh_unit_assert(queue->data != old_data); + + /* Check queue content */ + bh_unit_assert(BH_PTR_TO_INT(bh_queue_front(queue)) == 1337); + + /* Shrink to 0 (deallocate) */ + bh_queue_clear(queue); + bh_unit_assert(queue->size == 0); + + bh_unit_assert(bh_queue_reserve(queue, 0) == 0); + bh_unit_assert(queue->capacity == 0); + bh_unit_assert(queue->size == 0); + bh_unit_assert(bh_queue_empty(queue) == 1); + bh_unit_assert(queue->data == NULL); + + bh_queue_free(queue); + return 0; +} + +static int insert_remove(void) +{ + bh_queue_t *queue; + size_t i, added, removed; + void *iter; + + queue = bh_queue_new(); + bh_unit_assert(queue != NULL); + + added = 0; + for (i = 0; i < 256; i++) + { + added += i * 2; + bh_unit_assert(bh_queue_insert(queue, BH_INT_TO_PTR(i * 2)) == 0); + } + + removed = 0; + iter = bh_queue_iter_next(queue, NULL); + while (iter) + { + removed += BH_PTR_TO_INT(bh_queue_front(queue)); + bh_queue_remove(queue); + iter = bh_queue_iter_next(queue, NULL); + } + + bh_unit_assert(added == removed); + bh_unit_assert(queue->size == 0); + + bh_queue_free(queue); + return 0; +} + +static int rollover(void) +{ + bh_queue_t *queue; + size_t i, j, capacity; + + queue = bh_queue_new(); + bh_unit_assert(queue != NULL); + + bh_unit_assert(bh_queue_reserve(queue, 128) == 0); + capacity = queue->capacity; + + for (i = 0; i < 128; i++) + { + for (j = 0; j < 3; j++) + bh_queue_remove(queue); + + for (j = 0; j < 4 && queue->size < 128; j++) + bh_unit_assert(bh_queue_insert(queue, BH_INT_TO_PTR(i * 4 + j)) == 0); + } + + bh_unit_assert(queue->size == 128); + bh_unit_assert(queue->capacity == capacity); + + bh_queue_free(queue); + return 0; +} + +static int fields(void) +{ + bh_queue_t *queue; + + queue = bh_queue_new(); + bh_unit_assert(queue != NULL); + + bh_unit_assert(bh_queue_insert(queue, BH_INT_TO_PTR(1337)) == 0); + bh_unit_assert(queue->size == 1); + bh_unit_assert(queue->capacity >= 1); + bh_unit_assert(bh_queue_size(queue) == queue->size); + bh_unit_assert(bh_queue_capacity(queue) == queue->capacity); + + bh_queue_free(queue); + return 0; +} + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + + bh_unit_add("init_destroy", init_destroy); + bh_unit_add("new_free", new_free); + bh_unit_add("grow_shrink", grow_shrink); + bh_unit_add("insert_remove", insert_remove); + bh_unit_add("rollover", rollover); + bh_unit_add("fields", fields); + + return bh_unit_run(); +} diff --git a/unit/CMakeLists.txt b/unit/CMakeLists.txt new file mode 100644 index 0000000..f1eef3e --- /dev/null +++ b/unit/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.10) + +# Project and C standard configuration +project(bhunit LANGUAGES C) +set(CMAKE_C_STANDARD 90) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# Disable extensions +set(CMAKE_C_EXTENSIONS OFF) + +# Library code +set(BHUNIT_SOURCE + src/unit.c +) + +set(BHUNIT_HEADER + include/bh/unit.h +) + +# Library +add_library(bhunit STATIC ${BHUNIT_SOURCE} ${BHUNIT_HEADER}) +target_include_directories(bhunit PUBLIC include) diff --git a/unit/include/bh/unit.h b/unit/include/bh/unit.h new file mode 100644 index 0000000..42d41cd --- /dev/null +++ b/unit/include/bh/unit.h @@ -0,0 +1,24 @@ +#ifndef BHLIB_UNIT_H +#define BHLIB_UNIT_H + +#include <stdio.h> + +typedef int (*bh_unit_cb_t)(void); + +#define bh_unit_assert(e) \ + if (!(e)) { \ + printf("%s:%d\t%s", __FILE__, __LINE__, #e); \ + return -1; \ + } + + +#define bh_unit_assert_delta(x, y, e) \ + if ((((x)>(y))?((x)-(y)):((y)-(x)))>(e)) { \ + printf("%s:%d\t%s", __FILE__, __LINE__, #x " == " #y); \ + return -1; \ + } + +void bh_unit_add(const char *name, bh_unit_cb_t func); +int bh_unit_run(void); + +#endif /* BHLIB_UNIT_H */ diff --git a/unit/src/unit.c b/unit/src/unit.c new file mode 100644 index 0000000..95de080 --- /dev/null +++ b/unit/src/unit.c @@ -0,0 +1,55 @@ +#include <bh/unit.h> +#include <stdlib.h> + +typedef struct bh_unit_s +{ + struct bh_unit_s *next; + const char *name; + bh_unit_cb_t func; +} bh_unit_t; + +static bh_unit_t *root = NULL; + +void bh_unit_add(const char *name, bh_unit_cb_t func) +{ + bh_unit_t *unit, *current; + + unit = malloc(sizeof(*unit)); + if (!unit) + return; + + unit->name = name; + unit->func = func; + unit->next = NULL; + + current = root; + while (current && current->next) + current = current->next; + + if (current) + current->next = unit; + else + root = unit; +} + +int bh_unit_run(void) +{ + bh_unit_t *current; + printf("Running tests...\n"); + + current = root; + while (current) + { + printf("%s\n", current->name); + if (current->func()) + { + printf("\tFAIL\n"); + return -1; + } + printf("\tPASS\n"); + current = current->next; + } + + return 0; +} + |
