singe/thirdparty/uthash/tests/test92.c
2023-10-23 19:38:18 -05:00

245 lines
7.2 KiB
C

#include <stdio.h>
#define HASH_NONFATAL_OOM 1
#include "uthash.h"
#undef uthash_malloc
#undef uthash_free
#undef uthash_nonfatal_oom
#define uthash_malloc(sz) alt_malloc(sz)
#define uthash_free(ptr,sz) alt_free(ptr)
#define uthash_nonfatal_oom(e) do{(e)->mem_failed=1;}while(0)
#define all_select(a) 1
typedef struct example_user_t {
int id;
int cookie;
UT_hash_handle hh;
UT_hash_handle hh2;
int mem_failed;
} example_user_t;
static int malloc_cnt = 0;
static int malloc_failed = 0;
static int free_cnt = 0;
static void *alt_malloc(size_t sz)
{
if (--malloc_cnt <= 0) {
malloc_failed = 1;
return 0;
}
malloc_failed = 0;
return malloc(sz);
}
static void alt_free(void *ptr) {
free_cnt++;
free(ptr);
}
static void complain(int index, example_user_t *users, example_user_t *user)
{
int expected_frees = (3 - index);
if (users) {
printf("%d: users hash must be empty\n", index);
}
if (user->hh.tbl) {
printf("%d hash table must be empty\n", index);
}
if (free_cnt != expected_frees) {
printf("%d Expected %d frees, only had %d\n", index, expected_frees, free_cnt);
}
if (user->mem_failed != 1) {
printf("%d Expected user->mem_failed(%d) to be 1\n", index, user->mem_failed);
}
}
int main()
{
example_user_t *users = NULL;
example_user_t *user = (example_user_t*)malloc(sizeof(example_user_t));
example_user_t *test;
example_user_t *users2 = NULL;
int id = 0;
int i;
int saved_cnt;
user->id = id;
#ifdef HASH_BLOOM
malloc_cnt = 3; // bloom filter must fail
user->mem_failed = 0;
user->hh.tbl = (UT_hash_table*)1;
free_cnt = 0;
HASH_ADD_INT(users, id, user);
complain(1, users, user);
#endif /* HASH_BLOOM */
malloc_cnt = 2; // bucket creation must fail
user->mem_failed = 0;
free_cnt = 0;
user->hh.tbl = (UT_hash_table*)1;
HASH_ADD_INT(users, id, user);
complain(2, users, user);
malloc_cnt = 1; // table creation must fail
user->mem_failed = 0;
free_cnt = 0;
user->hh.tbl = (UT_hash_table*)1;
HASH_ADD_INT(users, id, user);
complain(3, users, user);
malloc_cnt = 4; // hash must create OK
user->mem_failed = 0;
HASH_ADD_INT(users, id, user);
if (user->mem_failed) {
printf("mem_failed must be 0, not %d\n", user->mem_failed);
}
HASH_FIND_INT(users,&id,test);
if (!test) {
printf("test user ID %d not found\n", id);
}
if (HASH_COUNT(users) != 1) {
printf("Got HASH_COUNT(users)=%d, should be 1\n", HASH_COUNT(users));
}
// let's add users until expansion fails.
malloc_failed = 0;
free_cnt = 0;
malloc_cnt = 1;
for (id = 1; 1; ++id) {
user = (example_user_t*)malloc(sizeof(example_user_t));
user->id = id;
if (id >= 1000) {
// prevent infinite, or too long of a loop here
puts("too many allocs before memory request");
break;
}
user->hh.tbl = (UT_hash_table*)1;
HASH_ADD_INT(users, id, user);
if (malloc_failed) {
if (id < 10) {
puts("there is no way your bucket size is <= 10");
}
if (user->hh.tbl) {
puts("user->hh.tbl should be NULL after failure");
} else if (user->mem_failed != 1) {
printf("mem_failed should be 1 after failure, not %d\n", user->mem_failed);
}
if (free_cnt != 0) {
printf("Expected 0 frees, had %d\n", free_cnt);
}
// let's make sure all previous IDs are there.
for (i=0; i<id; ++i) {
HASH_FIND_INT(users,&i,test);
if (test == NULL) {
printf("test user ID %d not found\n", i);
}
}
// let's try to add again, but with mem_failed set to 0
user->hh.tbl = NULL;
user->mem_failed = 0;
malloc_failed = 0;
HASH_ADD_INT(users, id, user);
if (!malloc_failed) {
puts("malloc should have been attempted");
}
if (user->hh.tbl) {
puts("user->hh.tbl should be NULL after second failure");
} else if (user->mem_failed != 1) {
printf("mem_failed should be 1 after second failure, not %d\n", user->mem_failed);
}
break;
}
}
// let's test HASH_SELECT.
// let's double the size of the table we've already built.
saved_cnt = id;
if (HASH_COUNT(users) != (unsigned)saved_cnt) {
printf("Got HASH_COUNT(users)=%d, should be %d\n", HASH_COUNT(users), saved_cnt);
}
for (i=0; i < saved_cnt; i++) {
user = (example_user_t*)malloc(sizeof(example_user_t));
user->id = ++id;
malloc_cnt = 20; // don't fail
HASH_ADD_INT(users, id, user);
}
HASH_ITER(hh, users, user, test) {
user->mem_failed = 0;
}
// HASH_SELECT calls uthash_nonfatal_oom() with an argument of type (void*).
#undef uthash_nonfatal_oom
#define uthash_nonfatal_oom(e) do{((example_user_t*)e)->mem_failed=1;}while(0)
malloc_cnt = 0;
free_cnt = 0;
HASH_SELECT(hh2, users2, hh, users, all_select);
if (users2) {
puts("Nothing should have been copied into users2");
}
HASH_ITER(hh, users, user, test) {
if (user->hh2.tbl) {
printf("User ID %d has tbl at %p\n", user->id, (void*)user->hh2.tbl);
}
if (user->mem_failed != 1) {
printf("User ID %d has mem_failed(%d), should be 1\n", user->id, user->mem_failed);
}
user->mem_failed = 0;
}
malloc_cnt = 4;
HASH_SELECT(hh2, users2, hh, users, all_select);
// note about the above.
// we tried to stick up to 1,000 entries into users,
// and the malloc failed after saved_cnt. The bucket threshold must have
// been triggered. We then doubled the amount of entries in user,
// and just ran HASH_SELECT, trying to copy them into users2.
// because the order is different, and because we continue after
// failures, the bucket threshold may get triggered on arbitrary
// elements, depending on the hash function.
saved_cnt = 0;
HASH_ITER(hh, users, user, test) {
example_user_t * user2;
HASH_FIND(hh2, users2, &user->id, sizeof(int), user2);
if (user2) {
if (!user->hh2.tbl) {
printf("User ID %d has tbl==NULL\n", user->id);
}
if (user->mem_failed != 0) {
printf("User ID %d has mem_failed(%d), expected 0\n", user->id, user->mem_failed);
}
} else {
saved_cnt++;
if (user->hh2.tbl) {
printf("User ID %d has tbl at %p, expected 0\n", user->id, (void*)user->hh2.tbl);
}
if (user->mem_failed != 1) {
printf("User ID %d has mem_failed(%d), expected is 1\n", user->id, user->mem_failed);
}
}
}
if (saved_cnt + HASH_CNT(hh2, users2) != HASH_COUNT(users)) {
printf("Selected elements : %d + %d != %d\n",
saved_cnt, HASH_CNT(hh2, users2), HASH_COUNT(users));
}
puts("End");
return 0;
}