Initial commit

This commit is contained in:
2024-04-13 14:52:29 +03:00
commit ac5df0ebe9
20 changed files with 1821 additions and 0 deletions

79
.gitignore vendored Normal file
View File

@@ -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

45
CMakeLists.txt Normal file
View File

@@ -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)

12
LICENSE Normal file
View File

@@ -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.

2
README Normal file
View File

@@ -0,0 +1,2 @@
BHLib

22
include/bh/bh.h Normal file
View File

@@ -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 */

237
include/bh/hashmap.h Normal file
View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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 */

159
include/bh/queue.h Normal file
View File

@@ -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 */

83
main.c Normal file
View File

@@ -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;
}

14
scripts/coverage.sh Executable file
View File

@@ -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

2
scripts/trim-whitespace.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
find . \( -iname "*.h" -o -iname "*.c" \) -exec sed -i "s/[ \t]*$//" {} \;

343
src/hashmap.c Normal file
View File

@@ -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;
}

176
src/queue.c Normal file
View File

@@ -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;
}

20
tests/CMakeLists.txt Normal file
View File

@@ -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()

278
tests/src/hashmap.c Normal file
View File

@@ -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();
}

183
tests/src/queue.c Normal file
View File

@@ -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();
}

22
unit/CMakeLists.txt Normal file
View File

@@ -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)

24
unit/include/bh/unit.h Normal file
View File

@@ -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 */

55
unit/src/unit.c Normal file
View File

@@ -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;
}