hashlist: Move get_match() and sorting into a separate class

The main intention here is that we can change the hashtable_t
implementation without being impeded by the special requirements imposed
by get_match() and sorting the keys/items in buckets.
This commit is contained in:
Tobias Brunner
2020-07-20 13:50:11 +02:00
parent 4334f61284
commit d9944102f5
6 changed files with 386 additions and 224 deletions
+147 -40
View File
@@ -144,6 +144,25 @@ struct private_hashtable_t {
#endif
};
typedef struct private_hashlist_t private_hashlist_t;
/**
* Private data of a hashlist_t object.
*/
struct private_hashlist_t {
/**
* Public part of hash table.
*/
hashlist_t public;
/**
* Inherited private part of hash table (we get the public part too, but
* ignore it).
*/
private_hashtable_t super;
};
#ifdef HASHTABLE_PROFILER
#define lookup_start() \
@@ -361,7 +380,7 @@ static inline pair_t *find_key(private_hashtable_t *this, const void *key,
while (pair)
{
lookup_probing();
/* when keys are ordered, we compare all items so we can abort earlier
/* when keys are sorted, we compare all items so we can abort earlier
* even if the hash does not match, but only as long as we don't
* have a callback */
if (!use_callback && this->cmp)
@@ -445,13 +464,6 @@ METHOD(hashtable_t, get, void*,
return pair ? pair->value : NULL;
}
METHOD(hashtable_t, get_match, void*,
private_hashtable_t *this, const void *key, hashtable_equals_t match)
{
pair_t *pair = find_key(this, key, match, NULL, NULL);
return pair ? pair->value : NULL;
}
METHOD(hashtable_t, remove_, void*,
private_hashtable_t *this, const void *key)
{
@@ -595,51 +607,77 @@ static void destroy_internal(private_hashtable_t *this,
}
}
free(this->table);
free(this);
}
METHOD(hashtable_t, destroy, void,
private_hashtable_t *this)
{
destroy_internal(this, NULL);
free(this);
}
METHOD(hashtable_t, destroy_function, void,
private_hashtable_t *this, void (*fn)(void*,const void*))
{
destroy_internal(this, fn);
free(this);
}
/**
* Create a hash table
*/
static private_hashtable_t *hashtable_create_internal(hashtable_hash_t hash,
u_int size)
METHOD(hashtable_t, create_enumerator_hashlist, enumerator_t*,
private_hashlist_t *this)
{
private_hashtable_t *this;
return create_enumerator(&this->super);
}
INIT(this,
.public = {
.put = _put,
.get = _get,
.get_match = _get_match,
.remove = _remove_,
.remove_at = (void*)_remove_at,
.get_count = _get_count,
.create_enumerator = _create_enumerator,
.destroy = _destroy,
.destroy_function = _destroy_function,
},
.hash = hash,
);
METHOD(hashtable_t, put_hashlist, void*,
private_hashlist_t *this, const void *key, void *value)
{
return put(&this->super, key, value);
}
init_hashtable(this, size);
METHOD(hashtable_t, get_hashlist, void*,
private_hashlist_t *this, const void *key)
{
return get(&this->super, key);
}
#ifdef HASHTABLE_PROFILER
this->backtrace = backtrace_create(3);
#endif
METHOD(hashlist_t, get_match, void*,
private_hashlist_t *this, const void *key, hashtable_equals_t match)
{
pair_t *pair = find_key(&this->super, key, match, NULL, NULL);
return pair ? pair->value : NULL;
}
return this;
METHOD(hashtable_t, remove_hashlist, void*,
private_hashlist_t *this, const void *key)
{
return remove_(&this->super, key);
}
METHOD(hashtable_t, remove_at_hashlist, void,
private_hashlist_t *this, private_enumerator_t *enumerator)
{
remove_at(&this->super, enumerator);
}
METHOD(hashtable_t, get_count_hashlist, u_int,
private_hashlist_t *this)
{
return get_count(&this->super);
}
METHOD2(hashtable_t, hashlist_t, destroy_hashlist, void,
private_hashlist_t *this)
{
destroy_internal(&this->super, NULL);
free(this);
}
METHOD(hashtable_t, destroy_function_hashlist, void,
private_hashlist_t *this, void (*fn)(void*,const void*))
{
destroy_internal(&this->super, fn);
free(this);
}
/*
@@ -648,9 +686,78 @@ static private_hashtable_t *hashtable_create_internal(hashtable_hash_t hash,
hashtable_t *hashtable_create(hashtable_hash_t hash, hashtable_equals_t equals,
u_int size)
{
private_hashtable_t *this = hashtable_create_internal(hash, size);
private_hashtable_t *this;
this->equals = equals;
INIT(this,
.public = {
.put = _put,
.get = _get,
.remove = _remove_,
.remove_at = (void*)_remove_at,
.get_count = _get_count,
.create_enumerator = _create_enumerator,
.destroy = _destroy,
.destroy_function = _destroy_function,
},
.hash = hash,
.equals = equals,
);
init_hashtable(this, size);
#ifdef HASHTABLE_PROFILER
this->backtrace = backtrace_create(3);
#endif
return &this->public;
}
/**
* Create a hash table
*/
static private_hashlist_t *hashlist_create_internal(hashtable_hash_t hash,
u_int size)
{
private_hashlist_t *this;
INIT(this,
.public = {
.ht = {
.put = _put_hashlist,
.get = _get_hashlist,
.remove = _remove_hashlist,
.remove_at = (void*)_remove_at_hashlist,
.get_count = _get_count_hashlist,
.create_enumerator = _create_enumerator_hashlist,
.destroy = _destroy_hashlist,
.destroy_function = _destroy_function_hashlist,
},
.get_match = _get_match,
.destroy = _destroy_hashlist,
},
.super = {
.hash = hash,
}
);
init_hashtable(&this->super, size);
#ifdef HASHTABLE_PROFILER
this->super.backtrace = backtrace_create(3);
#endif
return this;
}
/*
* Described in header
*/
hashlist_t *hashlist_create(hashtable_hash_t hash, hashtable_equals_t equals,
u_int size)
{
private_hashlist_t *this = hashlist_create_internal(hash, size);
this->super.equals = equals;
return &this->public;
}
@@ -658,12 +765,12 @@ hashtable_t *hashtable_create(hashtable_hash_t hash, hashtable_equals_t equals,
/*
* Described in header
*/
hashtable_t *hashtable_create_sorted(hashtable_hash_t hash,
hashtable_cmp_t cmp, u_int size)
hashlist_t *hashlist_create_sorted(hashtable_hash_t hash,
hashtable_cmp_t cmp, u_int size)
{
private_hashtable_t *this = hashtable_create_internal(hash, size);
private_hashlist_t *this = hashlist_create_internal(hash, size);
this->cmp = cmp;
this->super.cmp = cmp;
return &this->public;
}
+61 -25
View File
@@ -24,6 +24,7 @@
#include <collections/enumerator.h>
typedef struct hashtable_t hashtable_t;
typedef struct hashlist_t hashlist_t;
/**
* Prototype for a function that computes the hash code from the given key.
@@ -124,25 +125,6 @@ struct hashtable_t {
*/
void *(*get)(hashtable_t *this, const void *key);
/**
* Returns the first value with a matching key, if the hash table contains
* such an entry, otherwise NULL is returned.
*
* Compared to get() the given match function is used to compare the keys
* for equality. The hash function does have to be devised specially in
* order to make this work if the match function compares keys differently
* than the equals/comparison function provided to the constructor.
*
* This basically allows to enumerate all entries with the same hash value
* in their key's order.
*
* @param key the key to match against
* @param match match function to be used when comparing keys
* @return the value, NULL if not found
*/
void *(*get_match)(hashtable_t *this, const void *key,
hashtable_equals_t match);
/**
* Removes the value with the given key from the hash table and returns the
* removed value (or NULL if no such value existed).
@@ -183,8 +165,52 @@ struct hashtable_t {
};
/**
* Creates an empty hash table object. Items in buckets are ordered in
* insertion order.
* Class implementing a hash table with ordered keys/items and special query
* method.
*
* @note The ordering only pertains to keys/items in the same bucket (with or
* without the same hash value), not to the order when enumerating.
*
* This is intended to be used with hash functions that intentionally return the
* same hash value for different keys so multiple items can be retrieved for a
* key.
*
* It's like storing sorted linked lists in a hash table but with less overhead.
*/
struct hashlist_t {
/**
* Implements the hash table interface.
*/
hashtable_t ht;
/**
* Returns the first value with a matching key if the hash table contains
* such an entry, otherwise NULL is returned.
*
* Compared to get() the given match function is used to compare the keys
* for equality. The hash function does have to be devised specially in
* order to make this work if the match function compares keys differently
* than the equals/comparison function provided to the constructor.
*
* This basically allows to enumerate all entries with the same hash value
* in their key's order.
*
* @param key the key to match against
* @param match match function to be used when comparing keys
* @return the value, NULL if not found
*/
void *(*get_match)(hashlist_t *this, const void *key,
hashtable_equals_t match);
/**
* Destroys a hash list object.
*/
void (*destroy)(hashlist_t *this);
};
/**
* Creates an empty hash table object.
*
* @param hash hash function
* @param equals equals function
@@ -195,15 +221,25 @@ hashtable_t *hashtable_create(hashtable_hash_t hash, hashtable_equals_t equals,
u_int size);
/**
* Creates an empty hash table object with keys in each bucket sorted according
* to the given comparison function.
* Creates an empty hash list object with each bucket's keys in insertion order.
*
* @param hash hash function
* @param equals equals function
* @param size initial size
* @return hashtable_t object
*/
hashlist_t *hashlist_create(hashtable_hash_t hash, hashtable_equals_t equals,
u_int size);
/**
* Creates an empty hash list object with sorted keys in each bucket.
*
* @param hash hash function
* @param cmp comparison function
* @param size initial size
* @return hashtable_t object.
*/
hashtable_t *hashtable_create_sorted(hashtable_hash_t hash,
hashtable_cmp_t cmp, u_int size);
hashlist_t *hashlist_create_sorted(hashtable_hash_t hash,
hashtable_cmp_t cmp, u_int size);
#endif /** HASHTABLE_H_ @}*/
+8 -6
View File
@@ -65,7 +65,7 @@ struct private_plugin_loader_t {
/**
* Hashtable for registered features, as registered_feature_t
*/
hashtable_t *features;
hashlist_t *features;
/**
* Loaded features (stored in reverse order), as provided_feature_t
@@ -911,14 +911,16 @@ static void register_features(private_plugin_loader_t *this,
{
case FEATURE_PROVIDE:
lookup.feature = feature;
registered = this->features->get(this->features, &lookup);
registered = this->features->ht.get(&this->features->ht,
&lookup);
if (!registered)
{
INIT(registered,
.feature = feature,
.plugins = linked_list_create(),
);
this->features->put(this->features, registered, registered);
this->features->ht.put(&this->features->ht, registered,
registered);
}
INIT(provided,
.entry = entry,
@@ -950,13 +952,13 @@ static void unregister_feature(private_plugin_loader_t *this,
registered_feature_t *registered, lookup;
lookup.feature = provided->feature;
registered = this->features->get(this->features, &lookup);
registered = this->features->ht.get(&this->features->ht, &lookup);
if (registered)
{
registered->plugins->remove(registered->plugins, provided, NULL);
if (registered->plugins->get_count(registered->plugins) == 0)
{
this->features->remove(this->features, &lookup);
this->features->ht.remove(&this->features->ht, &lookup);
registered->plugins->destroy(registered->plugins);
free(registered);
}
@@ -1444,7 +1446,7 @@ plugin_loader_t *plugin_loader_create()
},
.plugins = linked_list_create(),
.loaded = linked_list_create(),
.features = hashtable_create(
.features = hashlist_create(
(hashtable_hash_t)registered_feature_hash,
(hashtable_equals_t)registered_feature_equals, 64),
);
+145 -112
View File
@@ -19,17 +19,22 @@
#include <utils/chunk.h>
/*******************************************************************************
* string hash table functions
* hash table functions
*/
static u_int hash(char *key)
static u_int hash_match(char *key)
{
return chunk_hash(chunk_from_str(key));
return chunk_hash(chunk_create(key, 4));
}
static bool equals(char *key1, char *key2)
static bool equal_match(char *key1, char *key2)
{
return streq(key1, key2);
if (!strneq(key1, key2, 4))
{
return FALSE;
}
/* look for an item with a key < than what we look for */
return strcmp(key1, key2) >= 0;
}
/*******************************************************************************
@@ -38,17 +43,77 @@ static bool equals(char *key1, char *key2)
static hashtable_t *ht;
typedef enum {
/* regular string hash table */
HASHTABLE_REGULAR,
/* regular string hash list */
HASHLIST_REGULAR,
/* sorted string hash list */
HASHLIST_REGULAR_SORTED,
REGULAR_MAX,
/* hash table with only 4 characters hashed -> one bucket tests */
HASHTABLE_FUZZY = REGULAR_MAX,
/* hash list with only 4 characters hashed */
HASHLIST_FUZZY,
/* sorted string hash list with only 4 characters hashed */
HASHLIST_FUZZY_SORTED,
HASHTABLE_MAX,
} hashtable_type_t;
/**
* Create a specific hash table/list
*/
static hashtable_t *create_hashtable(int i)
{
hashlist_t *hl = NULL;
DESTROY_IF(ht);
switch (i)
{
case HASHTABLE_REGULAR:
ht = hashtable_create(hashtable_hash_str,
hashtable_equals_str, 0);
break;
case HASHLIST_REGULAR:
hl = hashlist_create(hashtable_hash_str,
hashtable_equals_str, 0);
break;
case HASHLIST_REGULAR_SORTED:
hl = hashlist_create_sorted(hashtable_hash_str,
(hashtable_cmp_t)strcmp, 0);
break;
case HASHTABLE_FUZZY:
ht = hashtable_create((hashtable_hash_t)hash_match,
hashtable_equals_str, 0);
break;
case HASHLIST_FUZZY:
hl = hashlist_create((hashtable_hash_t)hash_match,
hashtable_equals_str, 0);
break;
case HASHLIST_FUZZY_SORTED:
hl = hashlist_create_sorted((hashtable_hash_t)hash_match,
(hashtable_cmp_t)strcmp, 0);
break;
}
if (hl)
{
ht = &hl->ht;
}
ck_assert_int_eq(ht->get_count(ht), 0);
return ht;
}
START_SETUP(setup_ht)
{
ht = hashtable_create((hashtable_hash_t)hash,
(hashtable_equals_t)equals, 0);
ck_assert_int_eq(ht->get_count(ht), 0);
create_hashtable(_i);
}
END_SETUP
START_TEARDOWN(teardown_ht)
{
ht->destroy(ht);
ht = NULL;
}
END_TEARDOWN
@@ -86,28 +151,13 @@ END_TEST
* get_match
*/
static u_int hash_match(char *key)
{
return chunk_hash(chunk_create(key, 4));
}
static bool equal_match(char *key1, char *key2)
{
if (!strneq(key1, key2, 4))
{
return FALSE;
}
/* look for an item with a key < than what we look for */
return strcmp(key1, key2) >= 0;
}
START_TEST(test_get_match)
{
hashlist_t *hl;
char *k1 = "key1_a", *k2 = "key2", *k3 = "key1_b", *k4 = "key1_c";
char *v1 = "val1", *v2 = "val2", *v3 = "val3", *value;
ht = hashtable_create((hashtable_hash_t)hash_match,
(hashtable_equals_t)equals, 0);
hl = (hashlist_t*)create_hashtable(HASHLIST_FUZZY);
ht->put(ht, k1, v1);
ht->put(ht, k2, v2);
@@ -118,31 +168,31 @@ START_TEST(test_get_match)
ck_assert(streq(ht->get(ht, k3), v3));
ck_assert(value == NULL);
value = ht->get_match(ht, k1, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k1, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v1));
value = ht->get_match(ht, k2, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k2, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v2));
value = ht->get_match(ht, k3, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k3, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v1));
value = ht->get_match(ht, k4, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k4, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v1));
ht->destroy(ht);
}
END_TEST
START_TEST(test_get_match_remove)
{
hashlist_t *hl;
char *k1 = "key1_a", *k2 = "key2", *k3 = "key1_b", *k4 = "key1_c";
char *v1 = "val1", *v2 = "val2", *v3 = "val3", *value;
ht = hashtable_create((hashtable_hash_t)hash_match,
(hashtable_equals_t)equals, 0);
hl = (hashlist_t*)create_hashtable(HASHLIST_FUZZY);
/* by removing and reinserting the first item we verify that insertion
* order is adhered */
ht->put(ht, k1, v1);
ht->put(ht, k2, v2);
ht->put(ht, k3, v3);
@@ -153,31 +203,30 @@ START_TEST(test_get_match_remove)
ck_assert(streq(ht->get(ht, k2), v2));
ck_assert(streq(ht->get(ht, k3), v3));
value = ht->get_match(ht, k1, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k1, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v1));
value = ht->get_match(ht, k2, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k2, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v2));
value = ht->get_match(ht, k3, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k3, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v3));
value = ht->get_match(ht, k4, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k4, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v3));
ht->destroy(ht);
}
END_TEST
START_TEST(test_get_match_sorted)
{
hashlist_t *hl;
char *k1 = "key1_a", *k2 = "key2", *k3 = "key1_b", *k4 = "key1_c";
char *v1 = "val1", *v2 = "val2", *v3 = "val3", *value;
ht = hashtable_create_sorted((hashtable_hash_t)hash_match,
(hashtable_cmp_t)strcmp, 0);
hl = (hashlist_t*)create_hashtable(HASHLIST_FUZZY_SORTED);
/* since the keys are sorted, the insertion order doesn't matter */
ht->put(ht, k3, v3);
ht->put(ht, k2, v2);
ht->put(ht, k1, v1);
@@ -190,20 +239,18 @@ START_TEST(test_get_match_sorted)
ck_assert(streq(ht->get(ht, k3), v3));
ck_assert(streq(ht->get(ht, k4), v1));
value = ht->get_match(ht, k1, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k1, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v1));
value = ht->get_match(ht, k2, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k2, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v2));
value = ht->get_match(ht, k3, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k3, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v1));
value = ht->get_match(ht, k4, (hashtable_equals_t)equal_match);
value = hl->get_match(hl, k4, (hashtable_equals_t)equal_match);
ck_assert(value != NULL);
ck_assert(streq(value, v1));
ht->destroy(ht);
}
END_TEST
@@ -252,38 +299,6 @@ START_TEST(test_remove_one_bucket)
{
char *k1 = "key1_a", *k2 = "key1_b", *k3 = "key1_c";
ht->destroy(ht);
ht = hashtable_create((hashtable_hash_t)hash_match,
(hashtable_equals_t)equals, 0);
do_remove(k1, k2, k3);
do_remove(k3, k2, k1);
do_remove(k1, k3, k2);
}
END_TEST
START_TEST(test_remove_sorted)
{
char *k1 = "key1", *k2 = "key2", *k3 = "key3";
ht->destroy(ht);
ht = hashtable_create_sorted((hashtable_hash_t)hash,
(hashtable_cmp_t)strcmp, 0);
do_remove(k1, k2, k3);
do_remove(k3, k2, k1);
do_remove(k1, k3, k2);
}
END_TEST
START_TEST(test_remove_sorted_one_bucket)
{
char *k1 = "key1_a", *k2 = "key1_b", *k3 = "key1_c";
ht->destroy(ht);
ht = hashtable_create_sorted((hashtable_hash_t)hash_match,
(hashtable_cmp_t)strcmp, 0);
do_remove(k1, k2, k3);
do_remove(k3, k2, k1);
do_remove(k1, k3, k2);
@@ -404,15 +419,10 @@ START_TEST(test_remove_at_one_bucket)
{
char *k1 = "key1_a", *k2 = "key1_b", *k3 = "key1_c";
ht->destroy(ht);
ht = hashtable_create((hashtable_hash_t)hash_match,
(hashtable_equals_t)equals, 0);
do_remove_at(k1, k2, k3);
}
END_TEST
/*******************************************************************************
* many items
*/
@@ -432,34 +442,63 @@ static int cmp_int(int *key1, int *key2)
return *key1 - *key2;
}
/**
* Create a specific hash table with integers as keys.
*/
static hashtable_t *create_int_hashtable(int i)
{
hashlist_t *hl = NULL;
DESTROY_IF(ht);
switch (i)
{
case HASHTABLE_REGULAR:
ht = hashtable_create((hashtable_hash_t)hash_int,
(hashtable_equals_t)equals_int, 0);
break;
case HASHLIST_REGULAR:
hl = hashlist_create((hashtable_hash_t)hash_int,
(hashtable_equals_t)equals_int, 0);
break;
case HASHLIST_REGULAR_SORTED:
hl = hashlist_create_sorted((hashtable_hash_t)hash_int,
(hashtable_cmp_t)cmp_int, 0);
break;
}
if (hl)
{
ht = &hl->ht;
}
ck_assert_int_eq(ht->get_count(ht), 0);
return ht;
}
START_SETUP(setup_ht_many)
{
ht = hashtable_create((hashtable_hash_t)hash_int,
(hashtable_equals_t)equals_int, 0);
ck_assert_int_eq(ht->get_count(ht), 0);
create_int_hashtable(_i >> 1);
}
END_SETUP
START_SETUP(setup_ht_many_cmp)
START_SETUP(setup_ht_lookups)
{
ht = hashtable_create_sorted((hashtable_hash_t)hash_int,
(hashtable_cmp_t)cmp_int, 0);
ck_assert_int_eq(ht->get_count(ht), 0);
create_int_hashtable(_i);
}
END_SETUP
START_TEARDOWN(teardown_ht_many)
{
ht->destroy_function(ht, (void*)free);
ht = NULL;
}
END_TEARDOWN
START_TEST(test_many_items)
{
u_int count = 250000;
u_int count = 100000;
int i, *val, r;
#define GET_VALUE(i) ({ _i == 0 ? i : (count-1-i); })
#define GET_VALUE(i) ({ (_i % 2) == 0 ? i : (count-1-i); })
for (i = 0; i < count; i++)
{
@@ -587,10 +626,11 @@ Suite *hashtable_suite_create()
tc = tcase_create("put/get");
tcase_add_checked_fixture(tc, setup_ht, teardown_ht);
tcase_add_test(tc, test_put_get);
tcase_add_loop_test(tc, test_put_get, 0, HASHTABLE_MAX);
suite_add_tcase(s, tc);
tc = tcase_create("get_match");
tcase_add_checked_fixture(tc, NULL, teardown_ht);
tcase_add_test(tc, test_get_match);
tcase_add_test(tc, test_get_match_remove);
tcase_add_test(tc, test_get_match_sorted);
@@ -598,39 +638,32 @@ Suite *hashtable_suite_create()
tc = tcase_create("remove");
tcase_add_checked_fixture(tc, setup_ht, teardown_ht);
tcase_add_test(tc, test_remove);
tcase_add_test(tc, test_remove_one_bucket);
tcase_add_test(tc, test_remove_sorted);
tcase_add_test(tc, test_remove_sorted_one_bucket);
tcase_add_loop_test(tc, test_remove, 0, REGULAR_MAX);
tcase_add_loop_test(tc, test_remove_one_bucket, HASHTABLE_FUZZY, HASHTABLE_MAX);
suite_add_tcase(s, tc);
tc = tcase_create("enumerator");
tcase_add_checked_fixture(tc, setup_ht, teardown_ht);
tcase_add_test(tc, test_enumerator);
tcase_add_loop_test(tc, test_enumerator, 0, HASHTABLE_MAX);
suite_add_tcase(s, tc);
tc = tcase_create("remove_at");
tcase_add_checked_fixture(tc, setup_ht, teardown_ht);
tcase_add_test(tc, test_remove_at);
tcase_add_test(tc, test_remove_at_one_bucket);
tcase_add_loop_test(tc, test_remove_at, 0, REGULAR_MAX);
tcase_add_loop_test(tc, test_remove_at_one_bucket, HASHTABLE_FUZZY, HASHTABLE_MAX);
suite_add_tcase(s, tc);
tc = tcase_create("many items");
tcase_add_checked_fixture(tc, setup_ht_many, teardown_ht_many);
tcase_set_timeout(tc, 10);
tcase_add_loop_test(tc, test_many_items, 0, 2);
tcase_add_test(tc, test_many_lookups_success);
tcase_add_test(tc, test_many_lookups_failure_larger);
tcase_add_test(tc, test_many_lookups_failure_smaller);
tcase_add_loop_test(tc, test_many_items, 0, REGULAR_MAX << 1);
suite_add_tcase(s, tc);
tc = tcase_create("many items sorted");
tcase_add_checked_fixture(tc, setup_ht_many_cmp, teardown_ht_many);
tcase_set_timeout(tc, 10);
tcase_add_loop_test(tc, test_many_items, 0, 2);
tcase_add_test(tc, test_many_lookups_success);
tcase_add_test(tc, test_many_lookups_failure_larger);
tcase_add_test(tc, test_many_lookups_failure_smaller);
tc = tcase_create("many lookups");
tcase_add_checked_fixture(tc, setup_ht_lookups, teardown_ht_many);
tcase_add_loop_test(tc, test_many_lookups_success, 0, REGULAR_MAX);
tcase_add_loop_test(tc, test_many_lookups_failure_larger, 0, REGULAR_MAX);
tcase_add_loop_test(tc, test_many_lookups_failure_smaller, 0, REGULAR_MAX);
suite_add_tcase(s, tc);
return s;