Improve tests

This commit is contained in:
jun 2025-02-25 14:01:20 +01:00
parent c1e125dfcb
commit 6abe12c4c1
10 changed files with 655 additions and 1005 deletions

View File

@ -3,7 +3,6 @@ set(PROJECT_NAME libguf)
project(${PROJECT_NAME})
# set(SOURCES src/guf_str.c)
# add_library(${PROJECT_NAME} STATIC ${SOURCES})
# target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}}/lib/guf)
# target_include_directories(${PROJECT_NAME} PRIVATE src)
@ -19,10 +18,10 @@ if (NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin)
endif ()
add_executable(libguf_example src/test/guf_example.c src/test/guf_dict_impl.c)
add_executable(libguf_example src/test/example.c src/test/guf_dict_impl.c)
target_include_directories(libguf_example PRIVATE src src/test)
add_executable(libguf_test src/test/guf_test.cpp src/test/guf_dict_impl.c src/test/guf_dbuf_impl.c src/test/guf_rand_impl.c)
add_executable(libguf_test src/test/test.cpp src/test/guf_dbuf_impl.c src/test/guf_dict_impl.c src/test/guf_rand_impl.c src/test/guf_sort_impl.c)
target_include_directories(libguf_test PRIVATE src src/test)
if (NOT DEFINED MSVC)

View File

@ -138,8 +138,8 @@ GUF_FN_KEYWORDS void GUF_CAT(GUF_CNT_NAME, _erase)(GUF_CNT_NAME *dbuf, ptrdiff_t
GUF_FN_KEYWORDS bool GUF_CAT(GUF_CNT_NAME, _try_pop)(GUF_CNT_NAME *dbuf, guf_err *err);
GUF_FN_KEYWORDS void GUF_CAT(GUF_CNT_NAME, _pop)(GUF_CNT_NAME *dbuf);
GUF_FN_KEYWORDS GUF_T GUF_CAT(GUF_CNT_NAME, _try_pop_cpy)(GUF_CNT_NAME *dbuf, guf_cpy_opt cpy_opt, guf_err *err);
GUF_FN_KEYWORDS GUF_T GUF_CAT(GUF_CNT_NAME, _pop_cpy)(GUF_CNT_NAME *dbuf, guf_cpy_opt cpy_opt);
GUF_FN_KEYWORDS GUF_T GUF_CAT(GUF_CNT_NAME, _try_pop_move)(GUF_CNT_NAME *dbuf, guf_err *err);
GUF_FN_KEYWORDS GUF_T GUF_CAT(GUF_CNT_NAME, _pop_move)(GUF_CNT_NAME *dbuf);
GUF_FN_KEYWORDS GUF_T *GUF_CAT(GUF_CNT_NAME, _try_at)(GUF_CNT_NAME *dbuf, ptrdiff_t idx, guf_err *err);
GUF_FN_KEYWORDS GUF_T *GUF_CAT(GUF_CNT_NAME, _at)(GUF_CNT_NAME *dbuf, ptrdiff_t idx);
@ -651,12 +651,12 @@ GUF_FN_KEYWORDS void GUF_CAT(GUF_CNT_NAME, _pop)(GUF_CNT_NAME *dbuf)
GUF_CAT(GUF_CNT_NAME, _try_pop)(dbuf, NULL);
}
GUF_FN_KEYWORDS GUF_T GUF_CAT(GUF_CNT_NAME, _try_pop_cpy)(GUF_CNT_NAME *dbuf, guf_cpy_opt cpy_opt, guf_err *err)
GUF_FN_KEYWORDS GUF_T GUF_CAT(GUF_CNT_NAME, _try_pop_move)(GUF_CNT_NAME *dbuf, guf_err *err)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_CNT_NAME, _valid)(dbuf));
if (dbuf->size == 0) {
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_pop_cpy: Cannot pop from empty dbuf"));
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_pop_move: Cannot pop from empty dbuf"));
GUF_T dummy;
memset(&dummy, 0, sizeof(GUF_T));
return dummy;
@ -665,47 +665,27 @@ GUF_FN_KEYWORDS GUF_T GUF_CAT(GUF_CNT_NAME, _try_pop_cpy)(GUF_CNT_NAME *dbuf, gu
GUF_T *popped = dbuf->data + (dbuf->size - 1);
GUF_T popped_val;
GUF_T *dst = &popped_val;
if (!GUF_CAT(GUF_CNT_NAME, _copy_opt_available)(cpy_opt)) {
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in function " GUF_STRINGIFY(GUF_CAT(GUF_CNT_NAME, _copy_opt_available)) ": cpy_opt unavailable"));
memset(&popped_val, 0, sizeof(popped_val));
return popped_val;
} else if (cpy_opt == GUF_CPY_DEEP) {
#ifdef GUF_T_COPY
dst = GUF_T_COPY(dst, popped, GUF_CAT(GUF_CNT_NAME, _get_elem_ctx)(dbuf));
#else
GUF_ASSERT_RELEASE(false);
#endif
} else if (cpy_opt == GUF_CPY_MOVE) {
#ifdef GUF_T_MOVE
#if defined(GUF_T_MOVE)
dst = GUF_T_MOVE(dst, popped, GUF_CAT(GUF_CNT_NAME, _get_elem_ctx)(dbuf));
#else
GUF_ASSERT_RELEASE(false);
#endif
} else {
GUF_ASSERT_RELEASE(cpy_opt == GUF_CPY_VALUE);
*dst = *popped;
}
memset(popped, 0, sizeof(GUF_T));
#endif
if (!dst) {
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dbuf_try_pop_cpy: Failed on copy"));
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dbuf_try_pop_move: Failed on move"));
memset(&popped_val, 0, sizeof(GUF_T));
return popped_val; // Return a dummy value in case the copy failed (as we have to return something).
} else {
#ifdef GUF_T_FREE
if (cpy_opt == GUF_CPY_DEEP) {
GUF_T_FREE(popped, GUF_CAT(GUF_CNT_NAME, _get_elem_ctx)(dbuf));
}
#endif
dbuf->size--;
guf_err_set_if_not_null(err, GUF_ERR_NONE);
return popped_val;
}
}
GUF_FN_KEYWORDS GUF_T GUF_CAT(GUF_CNT_NAME, _pop_cpy)(GUF_CNT_NAME *dbuf, guf_cpy_opt cpy_opt)
GUF_FN_KEYWORDS GUF_T GUF_CAT(GUF_CNT_NAME, _pop_move)(GUF_CNT_NAME *dbuf)
{
return GUF_CAT(GUF_CNT_NAME, _try_pop_cpy)(dbuf, cpy_opt, NULL);
return GUF_CAT(GUF_CNT_NAME, _try_pop_move)(dbuf, NULL);
}

View File

@ -1,649 +0,0 @@
// #include <stdint.h>
// #include <stdbool.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdio.h>
// #include "guf_common.h"
// #include "guf_dict.h"
// /*
// FNV-1a (32 bit) hash function.
// Generally, you should always call csr_hash with GUF_HASH_INIT as the hash argument, unless you want to create "chains" of hashes.
// cf. http://www.isthe.com/chongo/tech/comp/fnv/ (last retrieved: 2023-11-30)
// */
// uint32_t guf_hash32(const void *data, size_t num_bytes, uint32_t hash)
// {
// GUF_ASSERT_RELEASE(data);
// const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think...
// const uint32_t FNV_32_PRIME = 16777619ul;
// for (size_t i = 0; i < num_bytes; ++i) {
// hash ^= data_bytes[i];
// hash *= FNV_32_PRIME;
// }
// return hash;
// }
// uint64_t guf_hash64(const void *data, size_t num_bytes, uint64_t hash)
// {
// GUF_ASSERT_RELEASE(data);
// const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think...
// const uint64_t FNV_64_PRIME = 1099511628211ull;
// for (size_t i = 0; i < num_bytes; ++i) {
// hash ^= data_bytes[i];
// hash *= FNV_64_PRIME;
// }
// return hash;
// }
// static inline size_t find_next_power_of_two(size_t num)
// {
// GUF_ASSERT_RELEASE(num > 0);
// size_t pof2 = 1;
// while (pof2 < num) {
// GUF_ASSERT_RELEASE(pof2 * 2 > pof2);
// pof2 *= 2;
// }
// return pof2;
// }
// static const guf_dict_kv_id KV_ID_NULL = GUF_DICT_HASH_MAX;
// static const guf_dict_kv_id KV_ID_TOMBSTONE = GUF_DICT_HASH_MAX - 1;
// static const guf_dict_kv_id KV_ID_MAX = GUF_DICT_HASH_MAX - 2;
// const guf_dict GUF_DICT_UNINITIALISED = {
// .capacity_kv_status = 0,
// .size = 0,
// .num_tombstones = 0,
// .keys = NULL,
// .vals = NULL,
// .val_funcs = {.eq = NULL, .hash = NULL, .cpy = NULL, .move = NULL, .free = NULL, .type_size = 0},
// .kv_status = NULL,
// .probe_t = 0,
// .max_load_fac_fx10 = 0,
// .max_probelen = 0,
// };
// static inline void *cpy_key(guf_dict *ht, void *dst, const void *src, bool default_cpy)
// {
// if (default_cpy || ht->key_funcs.cpy == NULL) { // Default copy.
// return memcpy(dst, src, ht->key_funcs.type_size);
// } else {
// return ht->key_funcs.cpy(dst, src);
// }
// }
// static inline void *cpy_val(guf_dict *ht, void *dst, const void *src, bool default_cpy)
// {
// if (dst == NULL || src == NULL) {
// GUF_ASSERT_RELEASE(ht->val_funcs.type_size == 0);
// return NULL;
// }
// if (default_cpy || ht->val_funcs.cpy == NULL) { // Default copy.
// return memcpy(dst, src, ht->val_funcs.type_size);
// } else {
// return ht->val_funcs.cpy(dst, src);
// }
// }
// static inline void *cpy_or_move_val(guf_dict *ht, void *dst, void *src, guf_dict_insert_opt opts)
// {
// if (dst == NULL || src == NULL) {
// GUF_ASSERT_RELEASE(ht->val_funcs.type_size == 0);
// return NULL;
// }
// if ((opts & GUF_DICT_MOVE_VAL)) {
// GUF_ASSERT_RELEASE(ht->val_funcs.move);
// return ht->val_funcs.move(dst, src);
// } else { // Default copy.
// return cpy_val(ht, dst, src, false);
// }
// }
// static inline void *cpy_or_move_key(guf_dict *ht, void *dst, void *src, guf_dict_insert_opt opts)
// {
// if ((opts & GUF_DICT_MOVE_KEY)) {
// GUF_ASSERT_RELEASE(ht->key_funcs.move);
// return ht->key_funcs.move(dst, src);
// } else {
// return cpy_key(ht, dst, src, false);
// }
// }
// static inline guf_hash_size_t key_hash(const guf_dict *ht, const void *key)
// {
// if (ht->key_funcs.hash) {
// return ht->key_funcs.hash(key);
// } else { // Default hash function.
// return guf_hash(key, ht->key_funcs.type_size, GUF_HASH_INIT);
// }
// }
// static inline bool kv_stat_occupied(guf_dict_kv_status kv_stat) {
// return kv_stat.kv_id != KV_ID_NULL && kv_stat.kv_id != KV_ID_TOMBSTONE;
// }
// static void *key_get(guf_dict *ht, guf_dict_kv_id idx)
// {
// GUF_ASSERT_RELEASE(idx <= KV_ID_MAX);
// char *ptr = (char*)ht->keys;
// return ptr + idx * ht->key_funcs.type_size;
// }
// static void *val_get(guf_dict *ht, guf_dict_kv_id idx)
// {
// if (ht->val_funcs.type_size == 0) {
// return NULL;
// }
// GUF_ASSERT_RELEASE(idx <= KV_ID_MAX);
// char *ptr = (char*)ht->vals;
// return ptr + idx * ht->val_funcs.type_size ;
// }
// static inline bool key_eq(guf_dict *ht, const void *key_a, const void *key_b)
// {
// if (ht->key_funcs.eq) {
// return ht->key_funcs.eq(key_a, key_b);
// } else { // Default equality function.
// return 0 == memcmp(key_a, key_b, ht->key_funcs.type_size);
// }
// }
// static inline bool key_eq_at(guf_dict *ht, size_t idx, const void *key, guf_hash_size_t hash_of_key)
// {
// GUF_ASSERT(idx < ht->capacity_kv_status);
// GUF_ASSERT(kv_stat_occupied(ht->kv_status[idx]));
// if (ht->kv_status[idx].k_hash == hash_of_key) { // Hashes match -> we check if the keys are actually equal.
// return key_eq(ht, key_get(ht, ht->kv_status[idx].kv_id), key);
// } else {
// // Hashes don't match -> early exit (we save a memory load from ht->kv_elems).
// return false;
// }
// }
// static inline size_t probe_offset(size_t probe_len, guf_dict_probe_type probe_t)
// {
// GUF_ASSERT(probe_len > 0);
// switch (probe_t)
// {
// case GUF_DICT_PROBE_LINEAR:
// default:
// return 1;
// case GUF_DICT_PROBE_QUADRATIC:
// /*
// Guaranteed to visit each index once for capacities which are powers of two.
// cf. https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/ (last-retrieved 2024-07-29)
// */
// return probe_len * (probe_len + 1) / 2; // 1, 3, 6, 10, 15, ... (starting from probe_len == 1)
// }
// }
// static inline size_t mod_pow2(size_t a, size_t b) // a mod b (with b being a power of two.)
// {
// GUF_ASSERT(b > 0);
// return a & (b - 1);
// }
// static size_t find_idx(guf_dict *ht, const void *key, bool *key_exists, bool find_first_free)
// {
// const guf_hash_size_t hash = key_hash(ht, key);
// size_t idx = mod_pow2(hash, ht->capacity_kv_status); // hash % ht->capacity
// const size_t start_idx = idx;
// size_t probe_len = 1;
// size_t first_tombstone_idx = SIZE_MAX;
// do {
// if (ht->kv_status[idx].kv_id == KV_ID_NULL) { // 1.) Empty.
// if (first_tombstone_idx != SIZE_MAX) {
// idx = first_tombstone_idx;
// }
// ht->max_probelen = GUF_MAX(probe_len, ht->max_probelen);
// GUF_ASSERT(!kv_stat_occupied(ht->kv_status[idx]));
// *key_exists = false;
// return idx;
// } else if (ht->kv_status[idx].kv_id == KV_ID_TOMBSTONE) { // 2.) Tombstone.
// if (first_tombstone_idx == SIZE_MAX) {
// first_tombstone_idx = idx;
// }
// if (find_first_free) {
// goto end;
// } else {
// goto probe;
// }
// } else if (key_eq_at(ht, idx, key, hash)) { // 3.) Key already exists.
// ht->max_probelen = GUF_MAX(probe_len, ht->max_probelen);
// GUF_ASSERT(kv_stat_occupied(ht->kv_status[idx]));
// *key_exists = true;
// return idx;
// } else { // 4.) Have to probe due to hash-collision (idx is already occupied, but not by the key).
// probe:
// idx = mod_pow2(idx + probe_offset(probe_len, ht->probe_t), ht->capacity_kv_status);
// ++probe_len;
// GUF_ASSERT_RELEASE(probe_len < UINT32_MAX);
// }
// } while (idx != start_idx);
// end:
// if (first_tombstone_idx != SIZE_MAX) { // Edge case: No empty slots, but found tombstone.
// ht->max_probelen = GUF_MAX(probe_len, ht->max_probelen);
// GUF_ASSERT(!kv_stat_occupied(ht->kv_status[first_tombstone_idx]));
// *key_exists = false;
// return first_tombstone_idx;
// }
// *key_exists = false;
// return SIZE_MAX; // Failed to find an idx.
// }
// static void insert_kv(guf_dict *ht, void *key, void *val, size_t idx, guf_dict_insert_opt opts, bool default_cpy_key, bool default_cpy_val)
// {
// GUF_ASSERT_RELEASE(idx < ht->capacity_kv_status);
// GUF_ASSERT_RELEASE(ht->kv_status[idx].kv_id == KV_ID_NULL || ht->kv_status[idx].kv_id == KV_ID_TOMBSTONE);
// GUF_ASSERT_RELEASE(!kv_stat_occupied(ht->kv_status[idx]));
// if (!default_cpy_key) {
// if (!cpy_or_move_key(ht, key_get(ht, idx), key, opts)) {
// cpy_key(ht, key_get(ht, idx), key, true);
// }
// } else {
// cpy_key(ht, key_get(ht, idx), key, true);
// }
// if (!default_cpy_val) {
// if (!cpy_or_move_val(ht, val_get(ht, idx), val, opts)) {
// cpy_val(ht, val_get(ht, idx), val, true);
// }
// } else {
// cpy_val(ht, val_get(ht, idx), val, true);
// }
// if (ht->kv_status[idx].kv_id == KV_ID_TOMBSTONE) {
// GUF_ASSERT_RELEASE(ht->num_tombstones > 0);
// --ht->num_tombstones;
// }
// ht->kv_status[idx].k_hash = key_hash(ht, key_get(ht, idx));
// ++ht->size;
// }
// static void update_v(guf_dict *ht, void *val, size_t idx, guf_dict_insert_opt opts)
// {
// GUF_ASSERT_RELEASE(idx < ht->capacity_kv_status);
// GUF_ASSERT_RELEASE(kv_stat_occupied(ht->kv_status[idx]));
// if (ht->val_funcs.free) {
// ht->val_funcs.free(val_get(ht, idx));
// }
// cpy_or_move_val(ht, val_get(ht, idx), val, opts);
// }
// bool guf_dict_init(guf_dict *ht, size_t start_capacity, const guf_dict_kv_funcs *key_funcs, const guf_dict_kv_funcs *val_funcs)
// {
// GUF_ASSERT_RELEASE(ht);
// GUF_ASSERT_RELEASE(ht->capacity_kv_status == 0 && ht->size == 0 && ht->num_tombstones == 0 && ht->max_probelen == 0);
// GUF_ASSERT_RELEASE(ht->keys == NULL && ht->vals == NULL);
// GUF_ASSERT_RELEASE(key_funcs && val_funcs);
// ht->key_funcs = *key_funcs;
// ht->val_funcs = *val_funcs;
// if (val_funcs->type_size == 0) {
// // TODO: is a set!
// }
// if (start_capacity < 1) {
// start_capacity = 1;
// }
// ht->capacity_kv_status = find_next_power_of_two(start_capacity);
// ht->size = ht->num_tombstones = 0;
// ht->max_probelen = 0;
// GUF_ASSERT_RELEASE(ht->key_funcs.type_size > 0);
// // GUF_ASSERT_RELEASE(ht->val_funcs.type_size > 0);
// ht->probe_t = GUF_DICT_PROBE_QUADRATIC;
// ht->max_load_fac_fx10 = GUF_DICT_MAX_LOAD_FAC_FX10_DEFAULT;
// GUF_ASSERT_RELEASE(ht->max_load_fac_fx10 != 0 && ht->max_load_fac_fx10 <= 1024);
// ht->keys = calloc(ht->capacity_kv_status, ht->val_funcs.type_size);
// if (!ht->keys) {
// return false;
// }
// ht->vals = NULL;
// if (ht->val_funcs.type_size > 0) {
// ht->vals = calloc(ht->capacity_kv_status, ht->val_funcs.type_size);
// if (!ht->vals) {
// free(ht->keys);
// return false;
// }
// }
// ht->kv_status = calloc(ht->capacity_kv_status, sizeof(uint8_t));
// if (!ht->kv_status) {
// free(ht->keys);
// free(ht->vals);
// return false;
// }
// for (size_t i = 0; i < ht->capacity_kv_status; ++i) {
// GUF_ASSERT(ht->kv_status[i].kv_id == KV_ID_NULL);
// }
// return ht;
// }
// bool guf_dict_insert(guf_dict *ht, void *key, void *val, guf_dict_insert_opt opts)
// {
// GUF_ASSERT_RELEASE(ht);
// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status);
// GUF_ASSERT_RELEASE(ht->keys && ht->vals && ht->kv_status);
// if ((opts & GUF_DICT_MOVE_KEY) && ht->key_funcs.move == NULL) {
// // Ignore -Wunused-value.
// fprintf(stderr, "guf_dict_insert: key_funcs.move is NULL while GUF_DICT_MOVE_KEY is set\n");
// GUF_ASSERT(false);
// return false;
// }
// if ((opts & GUF_DICT_MOVE_VAL) && ht->val_funcs.move == NULL) {
// // Ignore -Wunused-value.
// fprintf(stderr, "guf_dict_insert: val_funcs.move is NULL while GUF_DICT_MOVE_VAL is set\n");
// GUF_ASSERT(false);
// return false;
// }
// if (guf_dict_load_factor_fx10(ht) > ht->max_load_fac_fx10 || ht->capacity_kv_status == ht->size) { // Handle growth:
// const size_t new_cap = 2 * ht->capacity_kv_status; // TODO: Limit to MAX_CAPACITY
// bool overflow = new_cap <= ht->capacity_kv_status;
// GUF_ASSERT(!overflow);
// if (overflow) {
// return false;
// }
// void *new_keys = calloc(new_cap, ht->key_funcs.type_size);
// if (!new_keys) {
// return false;
// }
// void *new_vals = NULL;
// if (ht->val_funcs.type_size > 0) {
// new_vals = calloc(new_cap, ht->val_funcs.type_size);
// if (!new_vals) {
// free(new_keys);
// return false;
// }
// } else {
// GUF_ASSERT_RELEASE(ht->vals == NULL);
// }
// guf_dict_kv_status *new_kv_status = calloc(new_cap, sizeof(guf_dict_kv_status)); // No realloc here!
// if (!new_kv_status) {
// free(new_keys);
// free(new_vals);
// return false;
// }
// for (size_t i = 0; i < new_cap; ++i) {
// new_kv_status[i].kv_id = KV_ID_NULL;
// new_kv_status[i].k_hash = 0;
// }
// guf_dict ht_new = *ht;
// ht_new.kv_status = new_kv_status;
// ht_new.keys = new_keys;
// ht_new.vals = new_vals;
// ht_new.size = 0;
// ht_new.capacity_kv_status = new_cap;
// ht_new.max_probelen = 0;
// size_t new_size = 0;
// for (size_t i = 0; i < ht->capacity_kv_status; ++i) {
// if (kv_stat_occupied(ht->kv_status[i])) {
// bool key_exists = false;
// const size_t new_idx = find_idx(&ht_new, key_get(ht, i), &key_exists, true);
// GUF_ASSERT_RELEASE(new_idx != SIZE_MAX);
// GUF_ASSERT_RELEASE(!key_exists);
// bool dumb_copy_key = ht->key_funcs.move == NULL;
// bool dumb_copy_val = ht->val_funcs.move == NULL;
// insert_kv(&ht_new, key_get(ht, i), val_get(ht, i), new_idx, GUF_DICT_MOVE_KEY | GUF_DICT_MOVE_VAL, dumb_copy_key, dumb_copy_val);
// ++new_size;
// }
// }
// GUF_ASSERT_RELEASE(new_size == ht->size);
// free(ht->kv_status);
// free(ht->keys);
// free(ht->vals);
// *ht = ht_new;
// }
// bool key_exists = false;
// const size_t idx = find_idx(ht, key, &key_exists, true);
// GUF_ASSERT_RELEASE(idx != SIZE_MAX);
// GUF_ASSERT_RELEASE(!kv_stat_occupied(ht->kv_status[idx]));
// if (key_exists) {
// update_v(ht, val, idx, opts);
// } else {
// insert_kv(ht, key, val, idx, opts, false, false);
// }
// return true;
// }
// bool guf_dict_contains_key(const guf_dict *ht, const void *key)
// {
// GUF_ASSERT_RELEASE(ht);
// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status);
// GUF_ASSERT_RELEASE (ht->keys && ht->vals && ht->kv_status);
// bool key_exists = false;
// const size_t idx = find_idx((guf_dict*)ht, key, &key_exists, false); // TODO: const cast
// GUF_ASSERT_RELEASE(!(key_exists && idx == SIZE_MAX));
// return key_exists;
// }
// void *guf_dict_get_val(guf_dict *ht, const void *key)
// {
// GUF_ASSERT_RELEASE(ht);
// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status);
// GUF_ASSERT_RELEASE(ht->keys && ht->vals && ht->kv_status);
// if (ht->capacity_kv_status == 0 || ht->size == 0) {
// return NULL;
// }
// bool key_exists = false;
// const size_t idx = find_idx(ht, key, &key_exists, false);
// GUF_ASSERT_RELEASE(idx != SIZE_MAX);
// if (!key_exists) {
// return NULL;
// } else {
// GUF_ASSERT(kv_stat_occupied(ht->kv_status[idx]));
// return val_get(ht, idx);
// }
// }
// bool guf_dict_remove(guf_dict *ht, const void *key)
// {
// GUF_ASSERT_RELEASE(ht);
// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status);
// GUF_ASSERT_RELEASE(ht->keys && ht->vals && ht->kv_status);
// if (ht->size == 0) {
// return false;
// }
// bool key_exists = false;
// const size_t idx = find_idx(ht, key, &key_exists, false);
// if (!key_exists) {
// return false;
// }
// if (ht->key_funcs.free) {
// ht->key_funcs.free(key_get(ht, idx));
// }
// if (ht->val_funcs.free) {
// ht->val_funcs.free(val_get(ht, idx));
// }
// ht->kv_status[idx].kv_id = KV_ID_TOMBSTONE;
// --ht->size;
// ++ht->num_tombstones;
// GUF_ASSERT(ht->size + ht->num_tombstones <= ht->capacity_kv_status);
// return true;
// }
// uint32_t guf_dict_load_factor_fx10(const guf_dict *ht)
// {
// const uint64_t fx10_scale = 1024; // 2^10 (represents 1 in fx10 fixed point format).
// GUF_ASSERT_RELEASE(ht->capacity_kv_status >= ht->size);
// if (ht->capacity_kv_status == 0) {
// return 0;
// }
// size_t size = ht->size + ht->num_tombstones;
// // <=> size * fx_scale * fx_scale) / (ht->capacity * fx_scale)
// GUF_ASSERT(size <= ht->capacity_kv_status);
// uint64_t load_fac = (size * fx10_scale) / (uint64_t)(ht->capacity_kv_status);
// GUF_ASSERT_RELEASE(load_fac <= fx10_scale);
// return (uint32_t)load_fac;
// }
// double guf_dict_load_factor_double(const guf_dict *ht)
// {
// GUF_ASSERT_RELEASE(ht->capacity_kv_status >= ht->size);
// if (ht->capacity_kv_status == 0 || ht->capacity_kv_status == 0) {
// return 0.f;
// }
// size_t size = ht->size + ht->num_tombstones;
// GUF_ASSERT(size <= ht->capacity_kv_status);
// return size / (double)(ht->capacity_kv_status);
// }
// void guf_dict_free(guf_dict *ht)
// {
// if (!ht) {
// return;
// }
// if (!ht->kv_status && !ht->keys && !ht->vals && ht->size == 0) {
// return;
// }
// for (size_t i = 0; i < ht->size; ++i) { // TODO: ht->size, not ht->capacity ?
// if (ht->keys && ht->key_funcs.free) {
// ht->key_funcs.free(key_get(ht, i));
// }
// if (ht->vals && ht->val_funcs.free) {
// ht->val_funcs.free(val_get(ht, i));
// }
// }
// free(ht->keys);
// free(ht->vals);
// free(ht->kv_status);
// ht->keys = NULL;
// ht->vals = NULL;
// ht->kv_status = NULL;
// ht->capacity_kv_status = ht->size = ht->num_tombstones = ht->max_probelen = 0;
// *ht = GUF_DICT_UNINITIALISED;
// }
// guf_dict_iter guf_dict_iter_begin(guf_dict *ht)
// {
// guf_dict_iter iter = {.elems_seen = ht->size + 1, .idx = 0, .ht = ht, .key = NULL, .val = NULL};
// if (ht->size == 0) {
// return iter; // end iter
// }
// for (size_t idx = 0; idx < ht->capacity_kv_status; ++idx) {
// if (kv_stat_occupied(ht->kv_status[idx])) {
// iter.idx = idx;
// iter.elems_seen = 1;
// iter.key = key_get(iter.ht, iter.idx);
// iter.val = val_get(iter.ht, iter.idx);
// GUF_ASSERT_RELEASE(iter.key != NULL);
// return iter;
// }
// }
// return iter; // end iter
// }
// bool guf_dict_iter_is_end(guf_dict_iter *iter)
// {
// return iter->elems_seen == iter->ht->size + 1;
// }
// void guf_dict_iter_advance(guf_dict_iter *iter)
// {
// if (guf_dict_iter_is_end(iter)) {
// return;
// }
// if (iter->elems_seen == iter->ht->size) {
// ++iter->elems_seen;
// iter->key = NULL;
// iter->val = NULL;
// return;
// }
// GUF_ASSERT_RELEASE(iter->elems_seen < iter->ht->size);
// GUF_ASSERT_RELEASE(iter->idx < iter->ht->capacity_kv_status);
// GUF_ASSERT_RELEASE(iter->key);
// for (size_t idx = iter->idx + 1; idx < iter->ht->capacity_kv_status; ++idx) {
// if (kv_stat_occupied(iter->ht->kv_status[idx])) {
// iter->idx = idx;
// iter->key = key_get(iter->ht, iter->idx);
// iter->val = val_get(iter->ht, iter->idx);
// ++iter->elems_seen;
// return;
// }
// }
// GUF_ASSERT_RELEASE(false);
// iter->elems_seen = iter->ht->size + 1;
// }
// /*
// Removal of keys without tombstones (only would work for linear probing I think).
// cf. https://stackoverflow.com/questions/9127207/hash-table-why-deletion-is-difficult-in-open-addressing-scheme/24886657#24886657 (last-retrieved 2024-07-26)
// The following del function from https://github.com/attractivechaos/klib/blob/6f73c80c6409d6f91cdf66ec1a002177274da2e7/cpp/khashl.hpp#L142-L150 (last-retrieved 2024-07-26)
// int del(khint_t i) {
// khint_t j = i, k, mask, nb = n_buckets();
// if (keys == 0 || i >= nb) return 0;
// mask = nb - khint_t(1);
// while (1) {
// j = (j + khint_t(1)) & mask;
// if (j == i || !__kh_used(used, j)) break; //j==i only when the table is completely full
// k = __kh_h2b(Hash()(keys[j]), bits);
// if (k <= i || k > j)
// keys[i] = keys[j], i = j;
// }
// __kh_set_unused(used, i);
// --count;
// return 1;
// }
// cf. https://en.wikipedia.org/w/index.php?title=Hash_table&oldid=95275577 (last-retrieved 2024-07-26)
// Note:
// - For all records in a cluster, there must be no vacant slots between their natural hash position
// and their current position (else lookups will terminate before finding the record).
// - i is a vacant slot that might be invalidating this property for subsequent records in the cluster.
// - j is such a subsequent record.
// - k is the raw hash where the record at j would naturally land in the hash table if there were no collisions.
// - This test is asking if the record at j is invalidly positioned with respect
// to the required properties of a cluster now that i is vacant.
// */

17
src/test/guf_sort_impl.c Normal file
View File

@ -0,0 +1,17 @@
#include "guf_sort_impl.h"
#define GUF_T float
#define GUF_IMPL
#include "guf_sort.h"
#define GUF_T int32_t
#define GUF_IMPL
#include "guf_sort.h"
#define GUF_T int8_t
#define GUF_IMPL
#include "guf_sort.h"
#define GUF_T guf_cstr_heap
#define GUF_IMPL
#include "guf_sort.h"

17
src/test/guf_sort_impl.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef GUF_SORT_IMPL_H
#define GUF_SORT_IMPL_H
#include "guf_cstr.h"
#define GUF_T float
#include "guf_sort.h"
#define GUF_T int32_t
#include "guf_sort.h"
#define GUF_T int8_t
#include "guf_sort.h"
#define GUF_T guf_cstr_heap
#include "guf_sort.h"
#endif

View File

@ -1,321 +0,0 @@
#include <vector>
#include <unordered_map>
#include <string>
#include <cstdio>
#include <iostream>
#include <chrono>
#include <iomanip>
extern "C"
{
#include "guf_init.h"
#include "guf_alloc_libc.h"
#include "guf_dbuf_impl.h"
#include "guf_dict_impl.h"
#include "guf_rand.h"
}
struct Test
{
const std::string name {};
std::chrono::duration<float, std::milli> runtime_ms {0};
bool passed {false}, done {false};
size_t num_failed_checks {0}, num_passed_checks {0};
Test(std::string& name) : name{name} {}
virtual ~Test() = default;
bool operator==(const Test &other) const {return name == other.name;}
size_t total_checks() const {return num_passed_checks + num_failed_checks;}
virtual bool run() = 0;
friend std::ostream& operator<<(std::ostream &os, const Test& tst)
{
std::ios_base::fmtflags os_flags = os.flags();
std::streamsize os_precision = os.precision();
os << tst.name << ": " << (tst.passed ? "PASS" : "FAIL");
os << std::fixed << std::setprecision(2);
os << " (" << tst.num_passed_checks << "/" << tst.total_checks() << ") in " << tst.runtime_ms.count() << " ms";
os.precision(os_precision);
os.flags(os_flags);
return os;
}
};
template<>
struct std::hash<std::unique_ptr<Test>> {
std::size_t operator()(const std::unique_ptr<Test>& test)
{
if (!test.get()) {
return 0;
}
return std::hash<std::string>()(test.get()->name);
}
};
#define GUF_TEST_CHECK(COND, MSG, TEST_OBJ_PTR) do { \
if (!(COND)) { \
std::cerr << this->name << ": FAILED CHECK " << MSG << " (line " << __LINE__ << " file " << __FILE__ << ")\n"; \
TEST_OBJ_PTR->num_failed_checks++; \
} else { \
TEST_OBJ_PTR->num_passed_checks++; \
} \
} while (0);
struct DbufIntTest : public Test
{
DbufIntTest(std::string name) : Test(name) {};
private:
std::vector<int> dbuf_to_vec(dbuf_int *dbuf)
{
std::vector<int> vec;
GUF_CNT_FOREACH(dbuf, dbuf_int, it) {
vec.push_back(*it.ptr);
}
return vec;
}
void test_push(dbuf_int *dbuf, int n)
{
std::vector<int> vec = dbuf_to_vec(dbuf);
GUF_TEST_CHECK(std::ssize(vec) == dbuf->size, "test_push(): initial size", this);
for (int i = 0; i < n; ++i) {
dbuf_int_push_val(dbuf, i);
vec.push_back(i);
GUF_TEST_CHECK(*dbuf_int_back(dbuf) == vec.back(), "test_push(): equal while pushing", this);
}
ptrdiff_t i = 0;
GUF_CNT_FOREACH(dbuf, dbuf_int, it) {
GUF_TEST_CHECK(*it.ptr == vec.at(i++), "test_push(): equal forward iteration", this);
}
GUF_TEST_CHECK(i == dbuf->size, "test_push(): size after iterating", this);
i = dbuf->size - 1;
GUF_CNT_FOREACH_REVERSE(dbuf, dbuf_int, rit) {
GUF_TEST_CHECK(*rit.ptr == vec.at(i--), "test_push(): equal reverse iteration", this);
}
GUF_TEST_CHECK(i == -1, "test_push(): size after reverse iteration", this);
}
void test_insert_remove(int n)
{
dbuf_int dbuf = {};
dbuf_int_init(&dbuf, 0, &guf_allocator_libc);
std::vector<int> vec = dbuf_to_vec(&dbuf);
guf_err err = GUF_ERR_NONE;
dbuf_int_try_erase(&dbuf, 0, &err);
GUF_TEST_CHECK(err == GUF_ERR_IDX_RANGE, "test_remove(): erase empty test 1", this);
err = GUF_ERR_NONE;
dbuf_int_try_erase(&dbuf, 12, &err);
GUF_TEST_CHECK(err == GUF_ERR_IDX_RANGE, "test_remove(): erase empty test 2", this);
err = GUF_ERR_NONE;
dbuf_int_try_front(&dbuf, &err);
GUF_TEST_CHECK(err == GUF_ERR_IDX_RANGE, "test_remove(): erase empty test 3", this);
err = GUF_ERR_NONE;
dbuf_int_try_back(&dbuf, &err);
GUF_TEST_CHECK(err == GUF_ERR_IDX_RANGE, "test_remove(): erase empty back 4", this);
err = GUF_ERR_NONE;
dbuf_int_try_at(&dbuf, 0, &err);
GUF_TEST_CHECK(err == GUF_ERR_IDX_RANGE, "test_remove(): erase empty back 5", this);
for (int i = 0; i < n; ++i) {
dbuf_int_insert_val(&dbuf, i, i);
dbuf_int_insert_val(&dbuf, i * 2, 0);
dbuf_int_insert_val(&dbuf, i * 4, dbuf.size);
vec.insert(vec.begin() + i, i);
vec.insert(vec.begin(), i * 2);
vec.insert(vec.end(), i * 4);
}
GUF_TEST_CHECK(std::ssize(vec) == dbuf.size, "test_remove(): size after insert", this);
// Iterate
dbuf_int_iter it_dbuf = dbuf_int_begin(&dbuf);
std::vector<int>::const_iterator it_vec = vec.begin();
while (!dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec != vec.end()) {
GUF_TEST_CHECK(*it_dbuf.ptr == *it_vec, "test_remove(): iterate equal", this);
it_dbuf = dbuf_int_iter_next(&dbuf, it_dbuf, 1);
std::advance(it_vec, 1);
}
GUF_TEST_CHECK(dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec == vec.end(), "test_remove(): iterate equal 2", this);
// Step iterate.
it_dbuf = dbuf_int_begin(&dbuf);
it_vec = vec.begin();
while (!dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec != vec.end()) {
GUF_TEST_CHECK(*it_dbuf.ptr == *it_vec, "test_remove(): iterate step equal", this);
it_dbuf = dbuf_int_iter_next(&dbuf, it_dbuf, 7);
if (dbuf_int_iter_is_end(&dbuf, it_dbuf)) {
it_vec = vec.end();
} else {
std::advance(it_vec, 7);
}
}
GUF_TEST_CHECK(dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec == vec.end(), "test_remove(): iterat step equal 2", this);
// Reverse iterate.
dbuf_int_iter rit_dbuf = dbuf_int_rbegin(&dbuf);
std::vector<int>::const_reverse_iterator rit_vec = vec.crbegin();
while (!dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec != vec.crend()) {
GUF_TEST_CHECK(*rit_dbuf.ptr == *rit_vec, "test_remove(): reverse iterate equal", this);
rit_dbuf = dbuf_int_iter_next(&dbuf, rit_dbuf, 1);
std::advance(rit_vec, 1);
}
GUF_TEST_CHECK(dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec == vec.rend(), "test_remove(): reverse iterate equal 2", this);
// Reverse iterate step.
rit_dbuf = dbuf_int_rbegin(&dbuf);
rit_vec = vec.crbegin();
while (!dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec != vec.crend()) {
GUF_TEST_CHECK(*rit_dbuf.ptr == *rit_vec, "test_remove(): reverse iterate step equal", this);
rit_dbuf = dbuf_int_iter_next(&dbuf, rit_dbuf, 4);
if (dbuf_int_iter_is_end(&dbuf, rit_dbuf)) {
rit_vec = vec.rend();
} else {
std::advance(rit_vec, 4);
}
}
GUF_TEST_CHECK(dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec == vec.rend(), "test_remove(): reverse iterate step equal 2", this);
GUF_TEST_CHECK(dbuf.size == std::ssize(vec), "test_remove(): size equal", this);
for (ptrdiff_t i = 0; i < dbuf.size; i += 8) {
dbuf_int_erase(&dbuf, i);
dbuf_int_erase(&dbuf, 0);
dbuf_int_pop(&dbuf);
vec.erase(vec.begin() + i);
vec.erase(vec.begin() + 0);
vec.pop_back();
}
GUF_TEST_CHECK(dbuf.size == std::ssize(vec), "test_remove(): size equal after remove", this);
for (ptrdiff_t i = 0; i < dbuf.size; i += 8) {
GUF_TEST_CHECK(*dbuf_int_at(&dbuf, i) == vec.at(i), "test_remove(): equal after remove", this);
}
const ptrdiff_t size = dbuf.size;
for (ptrdiff_t i = 0; i < size; ++i) {
int a = dbuf_int_pop_cpy(&dbuf, GUF_CPY_VALUE);
int b = vec.back();
GUF_TEST_CHECK(a == b, "test_remove(): equal pop", this);
vec.pop_back();
}
GUF_TEST_CHECK(dbuf.size == 0 && vec.size() == 0, "test_remove(): equal after removing all", this);
dbuf_int_free(&dbuf, NULL);
GUF_TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 0 && !dbuf.data, "test_remove(): state after free", this);
}
public:
bool run() override
{
if (done) {
return passed;
}
auto t0 = std::chrono::high_resolution_clock::now();
dbuf_int dbuf {};
dbuf_int_init(&dbuf, 0, &guf_allocator_libc);
test_push(&dbuf, 256);
test_push(&dbuf, 128);
test_push(&dbuf, 17);
GUF_TEST_CHECK(dbuf.size == (256 + 128 + 17), "run(): size after push", this);
dbuf_int_free(&dbuf, NULL);
GUF_TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 0 && dbuf.data == NULL, "run(): size after free", this);
dbuf_int_init(&dbuf, 24, &guf_allocator_libc);
GUF_TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 24 && dbuf.data, "run(): capacity after init with capacity", this);
test_push(&dbuf, 365);
test_push(&dbuf, 4);
test_push(&dbuf, 25);
test_push(&dbuf, 64);
GUF_TEST_CHECK(dbuf.size == (365 + 4 + 25 + 64), "run(): size after push", this);
dbuf_int_free(&dbuf, NULL);
GUF_TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 0 && dbuf.data == NULL, "run(): size after free", this);
test_insert_remove(4);
test_insert_remove(5);
test_insert_remove(6);
test_insert_remove(7);
test_insert_remove(8);
test_insert_remove(9);
test_insert_remove(10);
test_insert_remove(11);
test_insert_remove(16);
test_insert_remove(17);
test_insert_remove(31);
test_insert_remove(32);
test_insert_remove(33);
test_insert_remove(64);
test_insert_remove(128);
test_insert_remove(400);
test_insert_remove(401);
test_insert_remove(512);
test_insert_remove(513);
test_insert_remove(601);
test_insert_remove(2048);
test_insert_remove(2049);
const auto delta_t = std::chrono::high_resolution_clock::now() - t0;
runtime_ms = std::chrono::duration_cast<decltype(runtime_ms)>(delta_t);
done = true;
passed = (num_failed_checks == 0);
return passed;
}
};
// TODO: use std::unordered_set maybe
std::unordered_map<std::string, std::unique_ptr<Test>> g_tests {};
void init_dbuf_tests()
{
if (std::unique_ptr<Test> test = std::make_unique<DbufIntTest>("DbufIntTest"); test.get() != NULL) {
g_tests.insert({test.get()->name, std::move(test)});
} else {
throw std::runtime_error("test is NULL");
}
}
int main()
{
init_dbuf_tests();
bool all_passed = true;
for (auto& [name, test] : g_tests) {
if (Test *tst = test.get(); test.get() != NULL) {
tst->run();
std::cout << "- " << *tst << "\n";
all_passed = all_passed && tst->passed;
} else {
throw std::runtime_error("tst is NULL");
}
}
return all_passed ? EXIT_SUCCESS : EXIT_FAILURE;
}

54
src/test/test.cpp Normal file
View File

@ -0,0 +1,54 @@
#include <unordered_set>
#include <string>
#include <cstdio>
#include <iostream>
extern "C" {
#include "guf_init.h"
}
#include "test_dbuf.hpp"
std::unordered_set<std::unique_ptr<Test>> g_tests {};
void init_tests()
{
std::unique_ptr<Test> test = std::make_unique<DbufIntTest>("DbufIntTest");
GUF_ASSERT_RELEASE(test.get());
g_tests.insert(std::move(test));
test = std::make_unique<DbufCstringTest>("DbufCstringTest");
GUF_ASSERT_RELEASE(test.get());
g_tests.insert(std::move(test));
}
int main()
{
init_tests();
std::cout << "Running " << g_tests.size() << " tests...\n";
size_t num_passed = 0;
for (auto &test : g_tests) {
Test *tst = test.get();
GUF_ASSERT_RELEASE(tst);
tst->before_run();
tst->run();
tst->after_run();
std::cout << "- " << *tst << "\n";
if (tst->passed) {
++num_passed;
}
}
const bool passed_all = (num_passed == g_tests.size());
GUF_ASSERT_RELEASE(num_passed <= g_tests.size());
if (passed_all) {
std::cout << "-> SUCCESS: Passed all (" << num_passed << "/" << g_tests.size() << ") tests.\n";
} else {
std::cout << "-> FAILURE: Failed " << (g_tests.size() - num_passed) << "/" << g_tests.size() << " tests.\n";
}
return passed_all ? EXIT_SUCCESS : EXIT_FAILURE;
}

106
src/test/test.hpp Normal file
View File

@ -0,0 +1,106 @@
#ifndef TEST_HPP
#define TEST_HPP
#include <stack>
#include <string>
#include <cstdio>
#include <iostream>
#include <chrono>
#include <iomanip>
#include "guf_common.h"
#define TEST_CHECK(COND) check((COND), GUF_STRINGIFY(COND), __LINE__, __FILE__)
struct Test
{
private:
std::chrono::steady_clock::time_point time_start, time_end;
protected:
std::stack<std::string> check_name_stack;
std::string full_check_name = "";
void push_check_name(const std::string& check_name)
{
check_name_stack.push(check_name);
full_check_name = full_check_name + "::" + check_name;
}
void pop_check_name()
{
const size_t sep_idx = full_check_name.rfind("::");
GUF_ASSERT_RELEASE(sep_idx != std::string::npos);
full_check_name = full_check_name.substr(0, sep_idx);
check_name_stack.pop();
}
bool check(bool cond, std::string_view msg, size_t line, std::string_view fname)
{
if (!cond) {
std::cerr << name << full_check_name << ": ";
std::cerr << "FAILED CHECK (" << msg << ") on line " << line << " in file " << fname << "\n"; \
++num_failed_checks;
} else {
++num_passed_checks;
}
return cond;
}
public:
const std::string name {};
std::chrono::duration<float, std::milli> runtime_ms {0};
bool passed {false}, done {false};
size_t num_failed_checks {0}, num_passed_checks {0};
Test(const std::string& name) : name{name} {}
virtual ~Test() = default;
size_t total_checks() const
{
return num_passed_checks + num_failed_checks;
}
virtual bool run() = 0;
void before_run()
{
time_start = std::chrono::high_resolution_clock::now();
}
void after_run()
{
time_end = std::chrono::high_resolution_clock::now();
runtime_ms = std::chrono::duration_cast<decltype(runtime_ms)>(time_end - time_start);
}
friend std::ostream& operator<<(std::ostream &os, const Test& tst)
{
std::ios_base::fmtflags os_flags = os.flags();
std::streamsize os_precision = os.precision();
os << tst.name << ": " << (tst.passed ? "PASS" : "FAIL");
os << std::fixed << std::setprecision(2);
os << " (" << tst.num_passed_checks << "/" << tst.total_checks() << ") in " << tst.runtime_ms.count() << " ms";
os.precision(os_precision);
os.flags(os_flags);
return os;
}
};
template<>
struct std::hash<std::unique_ptr<Test>> {
std::size_t operator()(const std::unique_ptr<Test>& test) const
{
if (test.get() == nullptr) {
return 0;
}
return std::hash<std::string>()(test.get()->name);
}
};
#endif

447
src/test/test_dbuf.hpp Normal file
View File

@ -0,0 +1,447 @@
#pragma once
#include <vector>
#include "test.hpp"
extern "C"
{
#include "guf_alloc_libc.h"
#include "guf_dbuf_impl.h"
}
struct DbufIntTest : public Test
{
DbufIntTest(std::string name) : Test(name) {};
private:
std::vector<int> dbuf_to_vec(dbuf_int *dbuf)
{
std::vector<int> vec;
GUF_CNT_FOREACH(dbuf, dbuf_int, it) {
vec.push_back(*it.ptr);
}
return vec;
}
void test_push(dbuf_int *dbuf, int n)
{
std::vector<int> vec = dbuf_to_vec(dbuf);
TEST_CHECK(std::ssize(vec) == dbuf->size);
for (int i = 0; i < n; ++i) {
dbuf_int_push_val(dbuf, i);
vec.push_back(i);
TEST_CHECK(*dbuf_int_back(dbuf) == vec.back());
}
ptrdiff_t i = 0;
GUF_CNT_FOREACH(dbuf, dbuf_int, it) {
TEST_CHECK(*it.ptr == vec.at(i++));
}
TEST_CHECK(i == dbuf->size);
i = dbuf->size - 1;
GUF_CNT_FOREACH_REVERSE(dbuf, dbuf_int, rit) {
TEST_CHECK(*rit.ptr == vec.at(i--));
}
TEST_CHECK(i == -1);
}
void test_insert_remove(int n)
{
dbuf_int dbuf = {};
dbuf_int_init(&dbuf, 0, &guf_allocator_libc);
std::vector<int> vec = dbuf_to_vec(&dbuf);
guf_err err = GUF_ERR_NONE;
dbuf_int_try_erase(&dbuf, 0, &err);
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
err = GUF_ERR_NONE;
dbuf_int_try_erase(&dbuf, 12, &err);
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
err = GUF_ERR_NONE;
dbuf_int_try_front(&dbuf, &err);
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
err = GUF_ERR_NONE;
dbuf_int_try_back(&dbuf, &err);
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
err = GUF_ERR_NONE;
dbuf_int_try_at(&dbuf, 0, &err);
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
for (int i = 0; i < n; ++i) {
dbuf_int_insert_val(&dbuf, i, i);
dbuf_int_insert_val(&dbuf, i * 2, 0);
dbuf_int_insert_val(&dbuf, i * 4, dbuf.size);
vec.insert(vec.begin() + i, i);
vec.insert(vec.begin(), i * 2);
vec.insert(vec.end(), i * 4);
}
TEST_CHECK(std::ssize(vec) == dbuf.size);
// Iterate
dbuf_int_iter it_dbuf = dbuf_int_begin(&dbuf);
std::vector<int>::const_iterator it_vec = vec.begin();
while (!dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec != vec.end()) {
TEST_CHECK(*it_dbuf.ptr == *it_vec);
it_dbuf = dbuf_int_iter_next(&dbuf, it_dbuf, 1);
std::advance(it_vec, 1);
}
TEST_CHECK(dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec == vec.end());
// Step iterate.
it_dbuf = dbuf_int_begin(&dbuf);
it_vec = vec.begin();
while (!dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec != vec.end()) {
TEST_CHECK(*it_dbuf.ptr == *it_vec);
it_dbuf = dbuf_int_iter_next(&dbuf, it_dbuf, 7);
if (dbuf_int_iter_is_end(&dbuf, it_dbuf)) {
it_vec = vec.end();
} else {
std::advance(it_vec, 7);
}
}
TEST_CHECK(dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec == vec.end());
// Reverse iterate.
dbuf_int_iter rit_dbuf = dbuf_int_rbegin(&dbuf);
std::vector<int>::const_reverse_iterator rit_vec = vec.crbegin();
while (!dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec != vec.crend()) {
TEST_CHECK(*rit_dbuf.ptr == *rit_vec);
rit_dbuf = dbuf_int_iter_next(&dbuf, rit_dbuf, 1);
std::advance(rit_vec, 1);
}
TEST_CHECK(dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec == vec.rend());
// Reverse iterate step.
rit_dbuf = dbuf_int_rbegin(&dbuf);
rit_vec = vec.crbegin();
while (!dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec != vec.crend()) {
TEST_CHECK(*rit_dbuf.ptr == *rit_vec);
rit_dbuf = dbuf_int_iter_next(&dbuf, rit_dbuf, 4);
if (dbuf_int_iter_is_end(&dbuf, rit_dbuf)) {
rit_vec = vec.rend();
} else {
std::advance(rit_vec, 4);
}
}
TEST_CHECK(dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec == vec.rend());
TEST_CHECK(dbuf.size == std::ssize(vec));
for (ptrdiff_t i = 0; i < dbuf.size; i += 8) {
dbuf_int_erase(&dbuf, i);
dbuf_int_erase(&dbuf, 0);
dbuf_int_pop(&dbuf);
vec.erase(vec.begin() + i);
vec.erase(vec.begin() + 0);
vec.pop_back();
}
TEST_CHECK(dbuf.size == std::ssize(vec));
for (ptrdiff_t i = 0; i < dbuf.size; i += 8) {
TEST_CHECK(*dbuf_int_at(&dbuf, i) == vec.at(i));
}
const ptrdiff_t size = dbuf.size;
for (ptrdiff_t i = 0; i < size; ++i) {
int a = dbuf_int_pop_move(&dbuf);
int b = vec.back();
TEST_CHECK(a == b);
vec.pop_back();
}
TEST_CHECK(dbuf.size == 0 && vec.size() == 0);
dbuf_int_free(&dbuf, NULL);
TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 0 && !dbuf.data);
}
public:
bool run() override
{
if (done) {
return passed;
}
dbuf_int dbuf {};
dbuf_int_init(&dbuf, 0, &guf_allocator_libc);
push_check_name("test_push");
test_push(&dbuf, 256);
test_push(&dbuf, 128);
test_push(&dbuf, 17);
TEST_CHECK(dbuf.size == (256 + 128 + 17));
dbuf_int_free(&dbuf, NULL);
TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 0 && dbuf.data == NULL);
dbuf_int_init(&dbuf, 24, &guf_allocator_libc);
TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 24 && dbuf.data);
test_push(&dbuf, 365);
test_push(&dbuf, 4);
test_push(&dbuf, 25);
test_push(&dbuf, 64);
TEST_CHECK(dbuf.size == (365 + 4 + 25 + 64));
dbuf_int_free(&dbuf, NULL);
TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 0 && dbuf.data == NULL);
pop_check_name();
push_check_name("insert_remove");
for (int n = 0; n <= 128; ++n) {
test_insert_remove(n);
}
test_insert_remove(400);
test_insert_remove(401);
test_insert_remove(512);
test_insert_remove(513);
test_insert_remove(601);
test_insert_remove(2048);
test_insert_remove(2049);
pop_check_name();
done = true;
passed = (num_failed_checks == 0);
return passed;
}
};
struct DbufCstringTest : public Test
{
DbufCstringTest(std::string name) : Test(name) {};
private:
void test_iter(std::vector<std::string>& str_vec, dbuf_heap_cstr *str_dbuf, int step = 1)
{
GUF_ASSERT_RELEASE(str_dbuf);
if (step <= 0) {
step = 1;
}
ptrdiff_t i = 0;
GUF_CNT_FOREACH(str_dbuf, dbuf_heap_cstr, it) {
char *str = *it.ptr;
TEST_CHECK(str_vec.at(i) == str);
++i;
}
TEST_CHECK(i == str_dbuf->size);
i = str_dbuf->size - 1;
GUF_CNT_FOREACH_REVERSE(str_dbuf, dbuf_heap_cstr, rit) {
char *str = *rit.ptr;
TEST_CHECK(str_vec.at(i) == str);
--i;
}
TEST_CHECK(i == -1);
dbuf_heap_cstr_iter it_dbuf = dbuf_heap_cstr_begin(str_dbuf);
std::vector<std::string>::iterator it_vec = str_vec.begin();
while (!dbuf_heap_cstr_iter_is_end(str_dbuf, it_dbuf)) {
TEST_CHECK(it_vec != str_vec.end());
TEST_CHECK(*it_vec == *it_dbuf.ptr);
it_dbuf = dbuf_heap_cstr_iter_next(str_dbuf, it_dbuf, step);
if (!dbuf_heap_cstr_iter_is_end(str_dbuf, it_dbuf)) {
std::advance(it_vec, step);
} else {
it_vec = str_vec.end();
}
}
TEST_CHECK(dbuf_heap_cstr_iter_is_end(str_dbuf, it_dbuf) && it_vec == str_vec.end());
dbuf_heap_cstr_iter rit_dbuf = dbuf_heap_cstr_rbegin(str_dbuf);
std::vector<std::string>::reverse_iterator rit_vec = str_vec.rbegin();
while (!dbuf_heap_cstr_iter_is_end(str_dbuf, rit_dbuf)) {
TEST_CHECK(rit_vec != str_vec.rend());
TEST_CHECK(*rit_vec == *rit_dbuf.ptr);
rit_dbuf = dbuf_heap_cstr_iter_next(str_dbuf, rit_dbuf, step);
if (!dbuf_heap_cstr_iter_is_end(str_dbuf, rit_dbuf)) {
std::advance(rit_vec, step);
} else {
rit_vec = str_vec.rend();
}
}
TEST_CHECK(dbuf_heap_cstr_iter_is_end(str_dbuf, rit_dbuf) && rit_vec == str_vec.rend());
for (i = 0; i < str_dbuf->size; ++i) {
char *str = *dbuf_heap_cstr_at(str_dbuf, i);
TEST_CHECK(str_vec.at(i) == str);
}
}
void test_push_insert_erase(int n, ptrdiff_t start_cap = 0)
{
std::vector<std::string> str_vec;
dbuf_heap_cstr str_dbuf {};
dbuf_heap_cstr_init(&str_dbuf, start_cap, &guf_allocator_libc);
for (int i = 0; i < n; ++i) {
constexpr int BUF_SZ = 128;
char buf[BUF_SZ];
memset(buf, '\0', BUF_SZ);
snprintf(buf, BUF_SZ, "This is string number %d", i);
guf_cstr_heap str = buf;
dbuf_heap_cstr_push(&str_dbuf, &str, GUF_CPY_DEEP);
dbuf_heap_cstr_push_val_cpy(&str_dbuf, str);
char *heap_buf = strdup("Move me plz");
dbuf_heap_cstr_push(&str_dbuf, &heap_buf, GUF_CPY_MOVE);
TEST_CHECK(heap_buf == NULL);
TEST_CHECK(strncmp(*dbuf_heap_cstr_back(&str_dbuf), "Move me plz", BUF_SZ) == 0);
TEST_CHECK(strncmp(*dbuf_heap_cstr_at(&str_dbuf, str_dbuf.size - 2), buf, BUF_SZ) == 0);
TEST_CHECK(strncmp(*dbuf_heap_cstr_at(&str_dbuf, str_dbuf.size - 3), buf, BUF_SZ) == 0);
str_vec.push_back(std::string{buf});
str_vec.push_back(std::string{buf});
str_vec.emplace_back("Move me plz");
}
TEST_CHECK(str_dbuf.size == std::ssize(str_vec));
TEST_CHECK(str_dbuf.size == 3 * n);
for (int i = 1; i <= 8; ++i) {
test_iter(str_vec, &str_dbuf, i);
}
test_iter(str_vec, &str_dbuf, str_dbuf.size);
test_iter(str_vec, &str_dbuf, str_dbuf.size - 1);
test_iter(str_vec, &str_dbuf, str_dbuf.size + 1);
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
TEST_CHECK(str_vec.at(i) == *dbuf_heap_cstr_at(&str_dbuf, i));
}
// Insert front.
for (ptrdiff_t i = 0; i < 16; ++i) {
char str[] = "front";
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, 0);
str_vec.insert(str_vec.begin(), std::string{str});
}
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
TEST_CHECK(str_vec.at(i) == *dbuf_heap_cstr_at(&str_dbuf, i));
}
// Insert back.
for (ptrdiff_t i = 0; i < 16; ++i) {
char str[] = "front";
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, str_dbuf.size);
str_vec.insert(str_vec.end(), std::string{str});
}
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
TEST_CHECK(str_vec.at(i) == *dbuf_heap_cstr_at(&str_dbuf, i));
}
// Insert at i.
char str[] = "guf";
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, str_dbuf.size / 2);
str_vec.insert(str_vec.begin() + str_vec.size() / 2, str);
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, str_dbuf.size / 4);
str_vec.insert(str_vec.begin() + str_vec.size() / 4, str);
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, 1);
str_vec.insert(str_vec.begin() + 1, str);
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, str_dbuf.size - 1);
str_vec.insert(str_vec.begin() + (str_vec.size() - 1), str);
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
TEST_CHECK(str_vec.at(i) == *dbuf_heap_cstr_at(&str_dbuf, i));
}
guf_err err = GUF_ERR_NONE;
dbuf_heap_cstr_try_insert_val_cpy(&str_dbuf, str, str_dbuf.size + 1, &err);
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
err = GUF_ERR_NONE;
dbuf_heap_cstr_try_insert_val_cpy(&str_dbuf, str, -1, &err);
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
err = GUF_ERR_NONE;
dbuf_heap_cstr_try_insert_val_cpy(&str_dbuf, str, str_dbuf.size + 2, &err);
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
TEST_CHECK(str_vec.at(i) == *dbuf_heap_cstr_at(&str_dbuf, i));
}
if (str_dbuf.size) {
dbuf_heap_cstr_erase(&str_dbuf, str_dbuf.size - 1);
str_vec.erase(str_vec.end() - 1);
}
ptrdiff_t to_rem = 8;
while (str_dbuf.size && to_rem--) {
dbuf_heap_cstr_erase(&str_dbuf, 0);
str_vec.erase(str_vec.begin());
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
if (str_dbuf.size) {
dbuf_heap_cstr_pop(&str_dbuf);
str_vec.pop_back();
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
}
if (str_dbuf.size) {
dbuf_heap_cstr_erase(&str_dbuf, str_dbuf.size / 2);
str_vec.erase(str_vec.begin() + (str_vec.size() / 2));
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
}
}
dbuf_heap_cstr_free(&str_dbuf, NULL);
TEST_CHECK(str_dbuf.size == 0 && str_dbuf.capacity == 0 && !str_dbuf.data);
}
public:
bool run()
{
for (int i = 1; i <= 32; ++i) {
test_push_insert_erase(i);
test_push_insert_erase(i, i - 1);
test_push_insert_erase(i, i + 1);
test_push_insert_erase(i, i);
test_push_insert_erase(i, i / 2);
}
test_push_insert_erase(2048);
test_push_insert_erase(2048, 11);
dbuf_heap_cstr str_dbuf = {};
dbuf_heap_cstr_init(&str_dbuf, 0, &guf_allocator_libc);
std::vector<std::string> str_vec {};
for (int i = 0; i < 512; ++i) {
char buf[128];
memset(buf, '\0', GUF_STATIC_BUF_SIZE(buf));
snprintf(buf, GUF_STATIC_BUF_SIZE(buf), "This is a pretty guf string (number %d)", i);
guf_cstr_heap str = buf;
dbuf_heap_cstr_push(&str_dbuf, &str, GUF_CPY_DEEP);
str_vec.push_back(std::string{buf});
}
for (int i = 0; i < str_dbuf.size + 16; ++i) {
test_iter(str_vec, &str_dbuf, i);
}
dbuf_heap_cstr_free(&str_dbuf, NULL);
TEST_CHECK(str_dbuf.size == 0 && str_dbuf.capacity == 0 && !str_dbuf.data);
done = true;
passed = (num_failed_checks == 0);
return passed;
}
};