blob: 8e0207d57e2406cbfc92e4393720c322d9b97e10 [file] [log] [blame]
/* libnih
*
* test_hash.c - test suite for nih/hash.c
*
* Copyright © 2009 Scott James Remnant <scott@netsplit.com>.
* Copyright © 2009 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <nih/test.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/list.h>
#include <nih/hash.h>
typedef struct hash_entry {
NihList list;
const char *key;
} HashEntry;
static NihList *
new_entry (void *parent,
const char *key)
{
HashEntry *entry;
entry = nih_new (parent, HashEntry);
nih_list_init (&entry->list);
entry->key = key;
return (NihList *)entry;
}
static const void *
my_key_function (NihList *entry)
{
return "foo";
}
static uint32_t
my_hash_function (const void *key)
{
static uint32_t hash = 0;
return hash++;
}
static int
my_cmp_function (const void *key1,
const void *key2)
{
return 0;
}
void
test_new (void)
{
NihHash *hash;
size_t i;
TEST_FUNCTION ("nih_hash_new");
/* Check that we can create a small hash table; a small prime number
* should be selected for the actual size, and that number of empty
* bins should be allocated as a child of the hash table.
*/
TEST_FEATURE ("with zero size");
TEST_ALLOC_FAIL {
hash = nih_hash_new (NULL, 0,
my_key_function,
my_hash_function,
my_cmp_function);
if (test_alloc_failed) {
TEST_EQ_P (hash, NULL);
continue;
}
TEST_ALLOC_SIZE (hash, sizeof(NihHash));
TEST_EQ_P (hash->key_function, my_key_function);
TEST_EQ_P (hash->hash_function, my_hash_function);
TEST_EQ_P (hash->cmp_function, my_cmp_function);
TEST_EQ (hash->size, 17);
TEST_NE_P (hash->bins, NULL);
TEST_ALLOC_PARENT (hash->bins, hash);
for (i = 0; i < hash->size; i++)
TEST_LIST_EMPTY (&hash->bins[i]);
nih_free (hash);
}
/* Check again with a medium size, which should pick a medium prime
* number.
*/
TEST_FEATURE ("with medium size");
TEST_ALLOC_FAIL {
hash = nih_hash_new (NULL, 600,
my_key_function,
my_hash_function,
my_cmp_function);
if (test_alloc_failed) {
TEST_EQ_P (hash, NULL);
continue;
}
TEST_EQ (hash->size, 331);
TEST_NE_P (hash->bins, NULL);
TEST_ALLOC_PARENT (hash->bins, hash);
for (i = 0; i < hash->size; i++)
TEST_LIST_EMPTY (&hash->bins[i]);
nih_free (hash);
}
/* Check with a much larger size, which should pick the largest prime
* that we know of.
*/
TEST_FEATURE ("with large size");
TEST_ALLOC_FAIL {
hash = nih_hash_new (NULL, 40000000,
my_key_function,
my_hash_function,
my_cmp_function);
if (test_alloc_failed) {
TEST_EQ_P (hash, NULL);
continue;
}
TEST_EQ (hash->size, 10250323);
TEST_NE_P (hash->bins, NULL);
TEST_ALLOC_PARENT (hash->bins, hash);
for (i = 0; i < hash->size; i++)
TEST_LIST_EMPTY (&hash->bins[i]);
nih_free (hash);
}
}
void
test_string_new (void)
{
NihHash *hash;
size_t i;
/* Check that we can create a small hash table; a small prime number
* should be selected for the actual size, and that number of empty
* bins should be allocated as a child of the hash table.
*/
TEST_FUNCTION ("nih_hash_string_new");
TEST_ALLOC_FAIL {
hash = nih_hash_string_new (NULL, 0);
if (test_alloc_failed) {
TEST_EQ_P (hash, NULL);
continue;
}
TEST_ALLOC_SIZE (hash, sizeof(NihHash));
TEST_EQ_P (hash->key_function,
(NihKeyFunction)nih_hash_string_key);
TEST_EQ_P (hash->hash_function,
(NihHashFunction)nih_hash_string_hash);
TEST_EQ_P (hash->cmp_function,
(NihCmpFunction)nih_hash_string_cmp);
TEST_EQ (hash->size, 17);
TEST_NE_P (hash->bins, NULL);
TEST_ALLOC_PARENT (hash->bins, hash);
for (i = 0; i < hash->size; i++)
TEST_LIST_EMPTY (&hash->bins[i]);
nih_free (hash);
}
}
void
test_add (void)
{
NihHash *hash;
NihList *entry1, *entry2, *entry3, *entry4, *ptr;
TEST_FUNCTION ("nih_hash_add");
hash = nih_hash_string_new (NULL, 0);
entry1 = new_entry (hash, "entry 1");
entry2 = new_entry (hash, "entry 2");
entry3 = new_entry (hash, "entry 1");
entry4 = new_entry (hash, "entry 4");
/* Check that we can add an entry to an empty hash table; it should
* be returned and turn up in the appropriate bin.
*/
TEST_FEATURE ("with empty hash");
ptr = nih_hash_add (hash, entry1);
TEST_EQ_P (ptr, entry1);
TEST_EQ_P (hash->bins[15].next, entry1);
TEST_EQ_P (entry1->next, &hash->bins[15]);
TEST_EQ_P (hash->bins[15].prev, entry1);
TEST_EQ_P (entry1->prev, &hash->bins[15]);
/* Check that we can add an entry to a populated hash table. */
TEST_FEATURE ("with non-empty hash");
nih_hash_add (hash, entry2);
TEST_EQ_P (hash->bins[14].next, entry2);
TEST_EQ_P (entry2->next, &hash->bins[14]);
TEST_EQ_P (hash->bins[14].prev, entry2);
TEST_EQ_P (entry2->prev, &hash->bins[14]);
/* Check that we can add an entry with a duplicate key, and it is
* added to the end of the same bin as the previous entry with that
* key.
*/
TEST_FEATURE ("with duplicate key");
nih_hash_add (hash, entry3);
TEST_EQ_P (hash->bins[15].next, entry1);
TEST_EQ_P (entry1->next, entry3);
TEST_EQ_P (entry3->next, &hash->bins[15]);
TEST_EQ_P (hash->bins[15].prev, entry3);
TEST_EQ_P (entry3->prev, entry1);
TEST_EQ_P (entry1->prev, &hash->bins[15]);
/* Check that nih_hash_add can rip an entry out of an existing list
* and place it in the hash table.
*/
TEST_FEATURE ("with entry already in a list");
ptr = nih_list_new (NULL);
nih_list_add (ptr, entry4);
nih_hash_add (hash, entry4);
TEST_EQ_P (ptr->next, ptr);
TEST_EQ_P (ptr->prev, ptr);
TEST_EQ_P (hash->bins[3].next, entry4);
TEST_EQ_P (entry4->next, &hash->bins[3]);
TEST_EQ_P (hash->bins[3].prev, entry4);
TEST_EQ_P (entry4->prev, &hash->bins[3]);
nih_free (hash);
nih_free (ptr);
}
void
test_add_unique (void)
{
NihHash *hash;
NihList *entry1, *entry2, *entry3, *entry4, *ptr;
TEST_FUNCTION ("nih_hash_add_unique");
hash = nih_hash_string_new (NULL, 0);
entry1 = new_entry (hash, "entry 1");
entry2 = new_entry (hash, "entry 2");
entry3 = new_entry (hash, "entry 1");
entry4 = new_entry (hash, "entry 4");
/* Check that we can add an entry to an empty hash table; it should
* be returned and turn up in the appropriate bin.
*/
TEST_FEATURE ("with empty hash");
ptr = nih_hash_add_unique (hash, entry1);
TEST_EQ_P (ptr, entry1);
TEST_EQ_P (hash->bins[15].next, entry1);
TEST_EQ_P (entry1->next, &hash->bins[15]);
TEST_EQ_P (hash->bins[15].prev, entry1);
TEST_EQ_P (entry1->prev, &hash->bins[15]);
/* Check that we can add an entry to a populated hash table. */
TEST_FEATURE ("with non-empty hash");
nih_hash_add_unique (hash, entry2);
TEST_EQ_P (hash->bins[14].next, entry2);
TEST_EQ_P (entry2->next, &hash->bins[14]);
TEST_EQ_P (hash->bins[14].prev, entry2);
TEST_EQ_P (entry2->prev, &hash->bins[14]);
/* Check that we get NULL if we try and add an entry with a
* duplicate key, and that the hash table is not altered.
*/
TEST_FEATURE ("with duplicate key");
ptr = nih_hash_add_unique (hash, entry3);
TEST_EQ_P (ptr, NULL);
TEST_EQ_P (hash->bins[15].next, entry1);
TEST_EQ_P (entry1->next, &hash->bins[15]);
TEST_EQ_P (hash->bins[15].prev, entry1);
TEST_EQ_P (entry1->prev, &hash->bins[15]);
/* Check that nih_hash_add can rip an entry out of an existing list
* and place it in the hash table.
*/
TEST_FEATURE ("with entry already in a list");
ptr = nih_list_new (NULL);
nih_list_add (ptr, entry4);
nih_hash_add_unique (hash, entry4);
TEST_EQ_P (ptr->next, ptr);
TEST_EQ_P (ptr->prev, ptr);
TEST_EQ_P (hash->bins[3].next, entry4);
TEST_EQ_P (entry4->next, &hash->bins[3]);
TEST_EQ_P (hash->bins[3].prev, entry4);
TEST_EQ_P (entry4->prev, &hash->bins[3]);
nih_free (hash);
nih_free (ptr);
}
void
test_replace (void)
{
NihHash *hash;
NihList *entry1, *entry2, *entry3, *entry4, *ptr;
TEST_FUNCTION ("nih_hash_replace");
hash = nih_hash_string_new (NULL, 0);
entry1 = new_entry (hash, "entry 1");
entry2 = new_entry (hash, "entry 2");
entry3 = new_entry (hash, "entry 1");
entry4 = new_entry (hash, "entry 4");
/* Check that we can add an entry to an empty hash table; NULL should
* be returned (nothing replaced) and the entry should turn up in the
* appropriate bin.
*/
TEST_FEATURE ("with empty hash");
ptr = nih_hash_replace (hash, entry1);
TEST_EQ_P (ptr, NULL);
TEST_EQ_P (hash->bins[15].next, entry1);
TEST_EQ_P (entry1->next, &hash->bins[15]);
TEST_EQ_P (hash->bins[15].prev, entry1);
TEST_EQ_P (entry1->prev, &hash->bins[15]);
/* Check that we can add an entry to a populated hash table. */
TEST_FEATURE ("with non-empty hash");
nih_hash_replace (hash, entry2);
TEST_EQ_P (hash->bins[14].next, entry2);
TEST_EQ_P (entry2->next, &hash->bins[14]);
TEST_EQ_P (hash->bins[14].prev, entry2);
TEST_EQ_P (entry2->prev, &hash->bins[14]);
/* Check that we can add an entry with a duplicate key, replacing
* the existing one in the hash. The replaced entry should be
* returned, and removed from the bin.
*/
TEST_FEATURE ("with duplicate key");
ptr = nih_hash_replace (hash, entry3);
TEST_EQ_P (ptr, entry1);
TEST_EQ_P (entry1->next, entry1);
TEST_EQ_P (entry1->prev, entry1);
TEST_EQ_P (hash->bins[15].next, entry3);
TEST_EQ_P (entry3->next, &hash->bins[15]);
TEST_EQ_P (hash->bins[15].prev, entry3);
TEST_EQ_P (entry3->prev, &hash->bins[15]);
/* Check that nih_hash_add can rip an entry out of an existing list
* and place it in the hash table.
*/
TEST_FEATURE ("with entry already in a list");
ptr = nih_list_new (NULL);
nih_list_add (ptr, entry4);
nih_hash_replace (hash, entry4);
TEST_EQ_P (ptr->next, ptr);
TEST_EQ_P (ptr->prev, ptr);
TEST_EQ_P (hash->bins[3].next, entry4);
TEST_EQ_P (entry4->next, &hash->bins[3]);
TEST_EQ_P (hash->bins[3].prev, entry4);
TEST_EQ_P (entry4->prev, &hash->bins[3]);
nih_free (hash);
nih_free (ptr);
}
void
test_search (void)
{
NihHash *hash;
NihList *entry1, *entry2, *entry3, *ptr;
TEST_FUNCTION ("nih_hash_search");
hash = nih_hash_string_new (NULL, 0);
entry1 = nih_hash_add (hash, new_entry (hash, "entry 1"));
entry2 = nih_hash_add (hash, new_entry (hash, "entry 2"));
entry3 = nih_hash_add (hash, new_entry (hash, "entry 2"));
/* Check that we find the sole matching entry. */
ptr = nih_hash_search (hash, "entry 1", NULL);
TEST_EQ_P (ptr, entry1);
/* Searching again should find nothing */
ptr = nih_hash_search (hash, "entry 1", ptr);
TEST_EQ_P (ptr, NULL);
/* Check that where there's multiple matches, we find the first one. */
TEST_FEATURE ("with multiple matches");
ptr = nih_hash_search (hash, "entry 2", NULL);
TEST_EQ_P (ptr, entry2);
/* And that searching again finds the second one. */
ptr = nih_hash_search (hash, "entry 2", ptr);
TEST_EQ_P (ptr, entry3);
/* And again finds nothing. */
ptr = nih_hash_search (hash, "entry 2", ptr);
TEST_EQ_P (ptr, NULL);
/* Check that we get NULL if there are no matches. */
TEST_FEATURE ("with no matches");
ptr = nih_hash_search (hash, "entry 3", NULL);
TEST_EQ_P (ptr, NULL);
nih_free (hash);
}
void
test_lookup (void)
{
NihHash *hash;
NihList *entry1, *entry2, *entry3, *ptr;
TEST_FUNCTION ("nih_hash_lookup");
hash = nih_hash_string_new (NULL, 0);
entry1 = nih_hash_add (hash, new_entry (hash, "entry 1"));
entry2 = nih_hash_add (hash, new_entry (hash, "entry 2"));
entry3 = new_entry (hash, "entry 3");
/* Check that we find a single matching entry. */
TEST_FEATURE ("with single match");
ptr = nih_hash_lookup (hash, "entry 1");
TEST_EQ_P (ptr, entry1);
/* Check that we find the first matching entry. */
TEST_FEATURE ("with multiple matches");
ptr = nih_hash_lookup (hash, "entry 2");
TEST_EQ_P (ptr, entry2);
/* Check that we get NULL when there are no matching entries. */
TEST_FEATURE ("with no matches");
ptr = nih_hash_lookup (hash, "entry 3");
TEST_EQ_P (ptr, NULL);
nih_free (hash);
}
void
test_foreach (void)
{
NihHash *hash;
NihList *entry[4], *entry0, *entry1, *entry2, *entry3;
int i;
/* Check that NIH_HASH_FOREACH iterates the hash correctly in order,
* visiting each entry in each bin. Note that we stage the entries
* in the hash in the order we expect them to come out in, but add
* them in a different order for sanity.
*/
TEST_FUNCTION ("NIH_HASH_FOREACH");
hash = nih_hash_string_new (NULL, 0);
entry0 = entry[2] = new_entry (hash, "entry 1");
entry1 = entry[1] = new_entry (hash, "entry 2");
entry2 = entry[3] = new_entry (hash, "entry 1");
entry3 = entry[0] = new_entry (hash, "entry 4");
nih_hash_add (hash, entry0);
nih_hash_add (hash, entry1);
nih_hash_add (hash, entry2);
nih_hash_add (hash, entry3);
i = 0;
NIH_HASH_FOREACH (hash, iter) {
if (i > 3)
TEST_FAILED ("wrong number of iterations, expected %d got %d",
4, i + 1);
if (iter != entry[i])
TEST_FAILED ("wrong list entry, expected %p got %p",
entry[i], iter);
i++;
}
nih_free (hash);
}
void
test_foreach_safe (void)
{
NihHash *hash;
NihList *entry[4], *entry0, *entry1, *entry2, *entry3;
int i;
/* Check that NIH_HASH_FOREACH_SAFE iterates the hash correctly in
* order, visiting each entry in each bin; and that it's safe to
* remove the entries while doing so.
*/
TEST_FUNCTION ("NIH_HASH_FOREACH_SAFE");
hash = nih_hash_string_new (NULL, 0);
entry0 = entry[2] = new_entry (hash, "entry 1");
entry1 = entry[1] = new_entry (hash, "entry 2");
entry2 = entry[3] = new_entry (hash, "entry 1");
entry3 = entry[0] = new_entry (hash, "entry 4");
nih_hash_add (hash, entry0);
nih_hash_add (hash, entry1);
nih_hash_add (hash, entry2);
nih_hash_add (hash, entry3);
i = 0;
NIH_HASH_FOREACH_SAFE (hash, iter) {
if (i > 3)
TEST_FAILED ("wrong number of iterations, expected %d got %d",
4, i + 1);
if (iter != entry[i])
TEST_FAILED ("wrong list entry, expected %p got %p",
entry[i], iter);
nih_list_remove (entry[i]);
i++;
}
nih_free (hash);
}
void
test_string_key (void)
{
NihList *entry;
const char *key;
/* Check that the string key function returns a pointer to the
* key in our test structure.
*/
TEST_FUNCTION ("nih_hash_string_key");
entry = new_entry (NULL, "my entry");
key = nih_hash_string_key (entry);
TEST_EQ_P (key, ((HashEntry *)entry)->key);
TEST_EQ_STR (key, "my entry");
nih_free (entry);
}
int
main (int argc,
char *argv[])
{
test_new ();
test_string_new ();
test_add ();
test_add_unique ();
test_replace ();
test_search ();
test_lookup ();
test_foreach ();
test_foreach_safe ();
test_string_key ();
return 0;
}