diff --git a/CMakeLists.txt b/CMakeLists.txt index a00c23b..1971536 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/src/guf_dbuf.h b/src/guf_dbuf.h index 2e0321b..f3171e0 100644 --- a/src/guf_dbuf.h +++ b/src/guf_dbuf.h @@ -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 - 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); + #if defined(GUF_T_MOVE) + dst = GUF_T_MOVE(dst, popped, GUF_CAT(GUF_CNT_NAME, _get_elem_ctx)(dbuf)); + #else *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); } diff --git a/src/guf_dict.c b/src/guf_dict.c deleted file mode 100755 index ad26e69..0000000 --- a/src/guf_dict.c +++ /dev/null @@ -1,649 +0,0 @@ -// #include -// #include -// #include -// #include -// #include - -// #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. -// */ diff --git a/src/test/guf_example.c b/src/test/example.c similarity index 100% rename from src/test/guf_example.c rename to src/test/example.c diff --git a/src/test/guf_sort_impl.c b/src/test/guf_sort_impl.c new file mode 100644 index 0000000..2c034be --- /dev/null +++ b/src/test/guf_sort_impl.c @@ -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" diff --git a/src/test/guf_sort_impl.h b/src/test/guf_sort_impl.h new file mode 100644 index 0000000..83b2451 --- /dev/null +++ b/src/test/guf_sort_impl.h @@ -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 diff --git a/src/test/guf_test.cpp b/src/test/guf_test.cpp deleted file mode 100644 index d7007fe..0000000 --- a/src/test/guf_test.cpp +++ /dev/null @@ -1,321 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -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 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::size_t operator()(const std::unique_ptr& test) - { - if (!test.get()) { - return 0; - } - return std::hash()(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 dbuf_to_vec(dbuf_int *dbuf) - { - std::vector 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 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 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::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::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(delta_t); - - done = true; - passed = (num_failed_checks == 0); - return passed; - } -}; - -// TODO: use std::unordered_set maybe -std::unordered_map> g_tests {}; - -void init_dbuf_tests() -{ - if (std::unique_ptr test = std::make_unique("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; -} \ No newline at end of file diff --git a/src/test/test.cpp b/src/test/test.cpp new file mode 100644 index 0000000..e2c9677 --- /dev/null +++ b/src/test/test.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +extern "C" { + #include "guf_init.h" +} + +#include "test_dbuf.hpp" + +std::unordered_set> g_tests {}; + +void init_tests() +{ + std::unique_ptr test = std::make_unique("DbufIntTest"); + GUF_ASSERT_RELEASE(test.get()); + g_tests.insert(std::move(test)); + + test = std::make_unique("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; +} \ No newline at end of file diff --git a/src/test/test.hpp b/src/test/test.hpp new file mode 100644 index 0000000..2f3978a --- /dev/null +++ b/src/test/test.hpp @@ -0,0 +1,106 @@ +#ifndef TEST_HPP +#define TEST_HPP + +#include +#include +#include +#include +#include +#include + +#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 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 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(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::size_t operator()(const std::unique_ptr& test) const + { + if (test.get() == nullptr) { + return 0; + } + return std::hash()(test.get()->name); + } +}; + +#endif diff --git a/src/test/test_dbuf.hpp b/src/test/test_dbuf.hpp new file mode 100644 index 0000000..81d1874 --- /dev/null +++ b/src/test/test_dbuf.hpp @@ -0,0 +1,447 @@ +#pragma once +#include +#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 dbuf_to_vec(dbuf_int *dbuf) + { + std::vector 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 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 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::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::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& 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::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::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 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 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; + } +};