From: Wouter Wijngaards Date: Wed, 14 Mar 2007 14:30:30 +0000 (+0000) Subject: unit tests for hash table. X-Git-Tag: release-0.2~48 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=af41706579652a33aef2e608a3f630acacb1f67e;p=thirdparty%2Funbound.git unit tests for hash table. git-svn-id: file:///svn/unbound/trunk@179 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/Makefile.in b/Makefile.in index 0c095c631..c7c5e7d6d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -53,7 +53,7 @@ INSTALL=$(srcdir)/install-sh COMMON_SRC=$(wildcard services/*.c util/*.c util/data/*.c util/storage/*.c) util/configparser.c util/configlexer.c testcode/checklocks.c COMMON_OBJ=$(addprefix $(BUILD),$(COMMON_SRC:.c=.o)) COMPAT_OBJ=$(addprefix $(BUILD)compat/,$(LIBOBJS)) -UNITTEST_SRC=testcode/unitmain.c $(COMMON_SRC) +UNITTEST_SRC=$(wildcard testcode/unit*.c) $(COMMON_SRC) UNITTEST_OBJ=$(addprefix $(BUILD),$(UNITTEST_SRC:.c=.o)) $(COMPAT_OBJ) DAEMON_SRC=$(wildcard daemon/*.c) $(COMMON_SRC) DAEMON_OBJ=$(addprefix $(BUILD),$(DAEMON_SRC:.c=.o)) $(COMPAT_OBJ) diff --git a/doc/Changelog b/doc/Changelog index ff622daec..1dc4979ee 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 14 March 2007: Wouter - hash table insert (and subroutines) and lookup implemented. - hash table remove. + - unit tests for hash internal bin, lru functions. 13 March 2007: Wouter - lock_unprotect in checklocks. diff --git a/testcode/checklocks.c b/testcode/checklocks.c index dd9f8531d..e9b571872 100644 --- a/testcode/checklocks.c +++ b/testcode/checklocks.c @@ -66,8 +66,9 @@ static void lock_error(struct checked_lock* lock, log_err("lock error (description follows)"); log_err("Created at %s %s:%d", lock->create_func, lock->create_file, lock->create_line); - log_err("Previously %s %s:%d", lock->holder_func, - lock->holder_file, lock->holder_line); + if(lock->holder_func && lock->holder_file) + log_err("Previously %s %s:%d", lock->holder_func, + lock->holder_file, lock->holder_line); log_err("At %s %s:%d", func, file, line); log_err("Error for %s lock: %s", (lock->type==check_lock_mutex)?"mutex": ( diff --git a/testcode/unitlruhash.c b/testcode/unitlruhash.c new file mode 100644 index 000000000..e1dd94614 --- /dev/null +++ b/testcode/unitlruhash.c @@ -0,0 +1,257 @@ +/* + * testcode/unitlruhash.c - unit test for lruhash table. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Tests the locking LRU keeping hash table implementation. + */ + +#include "config.h" +#include "testcode/unitmain.h" +#include "util/log.h" +#include "util/storage/lruhash.h" + +/* --- test representation --- */ +/** structure contains test key */ +struct testkey { + /** the key id */ + int id; + /** the entry */ + struct lruhash_entry entry; +}; +/** structure contains test data */ +struct testdata { + /** data value */ + int data; +}; + +/** sizefunc for lruhash */ +static size_t test_sizefunc(void*, void*); +/** comparefunc for lruhash */ +static int test_compfunc(void*, void*); +/** delkey for lruhash */ +static void test_delkey(void*, void*); +/** deldata for lruhash */ +static void test_deldata(void*, void*); +/* --- end test representation --- */ + +/** hash func, very bad to improve collisions. */ +static hashvalue_t myhash(int id) {return id & 0x0f;} +/** allocate new key, fill in hash. */ +static struct testkey* newkey(int id) { + struct testkey* k = (struct testkey*)calloc(1, sizeof(struct testkey)); + k->id = id; + k->entry.hash = myhash(id); + k->entry.key = k; + lock_rw_init(&k->entry.lock); + return k; +} +/** new data el */ +static struct testdata* newdata(int val) { + struct testdata* d = (struct testdata*)calloc(1, + sizeof(struct testdata)); + d->data = val; + return d; +} +/** delete key */ +static void delkey(struct testkey* k) { + lock_rw_destroy(&k->entry.lock); free(k);} +/** delete data */ +static void deldata(struct testdata* d) {free(d);} + +/** test bin_find_entry function and bin_overflow_remove */ +static void +test_bin_find_entry(struct lruhash* table) +{ + struct testkey* k = newkey(12); + struct testdata* d = newdata(128); + struct testkey* k2 = newkey(12 + 1024); + struct testkey* k3 = newkey(14); + struct testkey* k4 = newkey(12 + 1024*2); + hashvalue_t h = myhash(12); + struct lruhash_bin bin; + bin_init(&bin, 1); + + /* remove from empty list */ + bin_overflow_remove(&bin, &k->entry); + + /* find in empty list */ + unit_assert( bin_find_entry(table, &bin, h, k) == NULL ); + + /* insert */ + lock_quick_lock(&bin.lock); + bin.overflow_list = &k->entry; + lock_quick_unlock(&bin.lock); + + /* find, hash not OK. */ + unit_assert( bin_find_entry(table, &bin, myhash(13), k) == NULL ); + + /* find, hash OK, but cmp not */ + unit_assert( k->entry.hash == k2->entry.hash ); + unit_assert( bin_find_entry(table, &bin, h, k2) == NULL ); + + /* find, hash OK, and cmp too */ + unit_assert( bin_find_entry(table, &bin, h, k) == &k->entry ); + + /* remove the element */ + lock_quick_lock(&bin.lock); + bin_overflow_remove(&bin, &k->entry); + lock_quick_unlock(&bin.lock); + unit_assert( bin_find_entry(table, &bin, h, k) == NULL ); + + /* prepend two different elements; so the list is long */ + /* one has the same hash, but different cmp */ + lock_quick_lock(&bin.lock); + unit_assert( k->entry.hash == k4->entry.hash ); + k4->entry.overflow_next = &k->entry; + k3->entry.overflow_next = &k4->entry; + bin.overflow_list = &k3->entry; + lock_quick_unlock(&bin.lock); + + /* find, hash not OK. */ + unit_assert( bin_find_entry(table, &bin, myhash(13), k) == NULL ); + + /* find, hash OK, but cmp not */ + unit_assert( k->entry.hash == k2->entry.hash ); + unit_assert( bin_find_entry(table, &bin, h, k2) == NULL ); + + /* find, hash OK, and cmp too */ + unit_assert( bin_find_entry(table, &bin, h, k) == &k->entry ); + + /* remove middle element */ + unit_assert( bin_find_entry(table, &bin, k4->entry.hash, k4) + == &k4->entry ); + lock_quick_lock(&bin.lock); + bin_overflow_remove(&bin, &k4->entry); + lock_quick_unlock(&bin.lock); + unit_assert( bin_find_entry(table, &bin, k4->entry.hash, k4) == NULL); + + /* remove last element */ + lock_quick_lock(&bin.lock); + bin_overflow_remove(&bin, &k->entry); + lock_quick_unlock(&bin.lock); + unit_assert( bin_find_entry(table, &bin, h, k) == NULL ); + + lock_quick_destroy(&bin.lock); + delkey(k); + delkey(k2); + delkey(k3); + delkey(k4); + deldata(d); +} + +/** test lru_front lru_remove */ +static void test_lru(struct lruhash* table) +{ + struct testkey* k = newkey(12); + struct testkey* k2 = newkey(14); + lock_quick_lock(&table->lock); + + unit_assert( table->lru_start == NULL && table->lru_end == NULL); + lru_remove(table, &k->entry); + unit_assert( table->lru_start == NULL && table->lru_end == NULL); + + /* add one */ + lru_front(table, &k->entry); + unit_assert( table->lru_start == &k->entry && + table->lru_end == &k->entry); + /* remove it */ + lru_remove(table, &k->entry); + unit_assert( table->lru_start == NULL && table->lru_end == NULL); + + /* add two */ + lru_front(table, &k->entry); + unit_assert( table->lru_start == &k->entry && + table->lru_end == &k->entry); + lru_front(table, &k2->entry); + unit_assert( table->lru_start == &k2->entry && + table->lru_end == &k->entry); + /* remove first in list */ + lru_remove(table, &k2->entry); + unit_assert( table->lru_start == &k->entry && + table->lru_end == &k->entry); + lru_front(table, &k2->entry); + unit_assert( table->lru_start == &k2->entry && + table->lru_end == &k->entry); + /* remove last in list */ + lru_remove(table, &k->entry); + unit_assert( table->lru_start == &k2->entry && + table->lru_end == &k2->entry); + + /* empty the list */ + lru_remove(table, &k2->entry); + unit_assert( table->lru_start == NULL && table->lru_end == NULL); + lock_quick_unlock(&table->lock); + delkey(k); + delkey(k2); +} + +void lruhash_test() +{ + /* start very very small array, so it can do lots of table_grow() */ + /* also small in size so that reclaim has to be done quickly. */ + struct lruhash* table = lruhash_create(2, 1024, + test_sizefunc, test_compfunc, test_delkey, test_deldata, NULL); + test_bin_find_entry(table); + test_lru(table); + /* hashtable tests go here */ + lruhash_delete(table); +} + +static size_t test_sizefunc(void* ATTR_UNUSED(key), void* ATTR_UNUSED(data)) +{ + return sizeof(struct testkey) + sizeof(struct testdata); +} + +static int test_compfunc(void* key1, void* key2) +{ + struct testkey* k1 = (struct testkey*)key1; + struct testkey* k2 = (struct testkey*)key2; + if(k1->id == k2->id) + return 0; + if(k1->id > k2->id) + return 1; + return -1; +} + +static void test_delkey(void* key, void* ATTR_UNUSED(arg)) +{ + delkey((struct testkey*)key); +} + +static void test_deldata(void* data, void* ATTR_UNUSED(arg)) +{ + deldata((struct testdata*)data); +} diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 08401c9ec..c51537716 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -41,11 +41,10 @@ #include "config.h" #include "util/log.h" +#include "testcode/unitmain.h" /** number of tests done */ int testcount = 0; -/** test bool x, exits on failure, increases testcount. */ -#define unit_assert(x) testcount++; log_assert(x); #include "util/alloc.h" /** test alloc code */ @@ -55,7 +54,6 @@ alloc_test() { struct alloc_cache major, minor1, minor2; int i; - checklock_start(); alloc_init(&major, NULL); alloc_init(&minor1, &major); alloc_init(&minor2, &major); @@ -84,7 +82,6 @@ alloc_test() { alloc_clear(&minor2); unit_assert(major.num_quar == 11); alloc_clear(&major); - checklock_stop(); } #include "util/net_help.h" @@ -148,9 +145,12 @@ main(int argc, char* argv[]) return 1; } printf("Start of %s unit test.\n", PACKAGE_STRING); + checklock_start(); net_test(); alloc_test(); msgreply_test(); + lruhash_test(); + checklock_stop(); printf("%d tests succeeded\n", testcount); return 0; } diff --git a/testcode/unitmain.h b/testcode/unitmain.h new file mode 100644 index 000000000..ae0b37057 --- /dev/null +++ b/testcode/unitmain.h @@ -0,0 +1,53 @@ +/* + * testcode/unitmain.h - unit test main program for unbound. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Declarations useful for the unit tests. + */ + +#ifndef TESTCODE_UNITMAIN_H +#define TESTCODE_UNITMAIN_H +#include "util/log.h" + +/** number of tests done */ +extern int testcount; +/** test bool x, exits on failure, increases testcount. */ +#define unit_assert(x) do {testcount++; log_assert(x);} while(0) + +/** unit test lruhashtable implementation */ +void lruhash_test(); + +#endif /* TESTCODE_UNITMAIN_H */ diff --git a/util/storage/lruhash.c b/util/storage/lruhash.c index e173a5f98..88e473463 100644 --- a/util/storage/lruhash.c +++ b/util/storage/lruhash.c @@ -43,91 +43,7 @@ #include "config.h" #include "util/storage/lruhash.h" -/* ------ local helper functions ------------- */ - -/** init the hash bins for the table. */ -static void bin_init(struct lruhash_bin* array, size_t size); - -/** delete the hash bin and entries inside it */ -static void bin_delete(struct lruhash* table, struct lruhash_bin* bin); - -/** find entry in hash bin. You must have locked the bin. - * @param table: hash table with function pointers. - * @param bin: hash bin to look into. - * @param hash: hash value to look for. - * @param key: key to look for. - * @return: the entry or NULL if not found. - */ -static struct lruhash_entry* bin_find_entry(struct lruhash* table, - struct lruhash_bin* bin, hashvalue_t hash, void* key); - -/** - * Remove entry from bin overflow chain. - * You must have locked the bin. - * @param bin: hash bin to look into. - * @param entry: entry ptr that needs removal. - */ -static void bin_overflow_remove(struct lruhash_bin* bin, - struct lruhash_entry* entry); - -/** - * Split hash bin into two new ones. Based on increased size_mask. - * Caller must hold hash table lock. - * At the end the routine acquires all hashbin locks (in the old array). - * This makes it wait for other threads to finish with the bins. - * So the bins are ready to be deleted after this function. - * @param table: hash table with function pointers. - * @param newa: new increased array. - * @param newmask: new lookup mask. - */ -static void bin_split(struct lruhash* table, struct lruhash_bin* newa, - int newmask); - -/** - * Try to make space available by deleting old entries. - * Assumes that the lock on the hashtable is being held by caller. - * Caller must not hold bin locks. - * @param table: hash table. - * @param list: list of entries that are to be deleted later. - * Entries have been removed from the hash table and writelock is held. - */ -static void reclaim_space(struct lruhash* table, struct lruhash_entry** list); - -/** - * Grow the table lookup array. Becomes twice as large. - * Caller must hold the hash table lock. Must not hold any bin locks. - * Tries to grow, on malloc failure, nothing happened. - * @param table: hash table. - */ -static void table_grow(struct lruhash* table); - -/** - * Touch entry, so it becomes the most recently used in the LRU list. - * Caller must hold hash table lock. The entry must be inserted already. - * @param table: hash table. - * @param entry: entry to make first in LRU. - */ -static void lru_touch(struct lruhash* table, struct lruhash_entry* entry); - -/** - * Put entry at front of lru. entry must be unlinked from lru. - * Caller must hold hash table lock. - * @param table: hash table with lru head and tail. - * @param entry: entry to make most recently used. - */ -static void lru_front(struct lruhash* table, struct lruhash_entry* entry); - -/** - * Remove entry from lru list. - * Caller must hold hash table lock. - * @param table: hash table with lru head and tail. - * @param entry: entry to remove from lru. - */ -static void lru_remove(struct lruhash* table, struct lruhash_entry* entry); - -/* ------ end local helper functions --------- */ - -static void +void bin_init(struct lruhash_bin* array, size_t size) { size_t i; @@ -173,7 +89,7 @@ lruhash_create(size_t start_size, size_t maxmem, lruhash_sizefunc_t sizefunc, return table; } -static void +void bin_delete(struct lruhash* table, struct lruhash_bin* bin) { struct lruhash_entry* p, *np; @@ -190,7 +106,7 @@ bin_delete(struct lruhash* table, struct lruhash_bin* bin) } } -static void +void bin_split(struct lruhash* table, struct lruhash_bin* newa, int newmask) { @@ -232,7 +148,7 @@ lruhash_delete(struct lruhash* table) free(table); } -static void +void bin_overflow_remove(struct lruhash_bin* bin, struct lruhash_entry* entry) { struct lruhash_entry* p = bin->overflow_list; @@ -247,7 +163,7 @@ bin_overflow_remove(struct lruhash_bin* bin, struct lruhash_entry* entry) } } -static void +void reclaim_space(struct lruhash* table, struct lruhash_entry** list) { struct lruhash_entry* d; @@ -280,7 +196,7 @@ reclaim_space(struct lruhash* table, struct lruhash_entry** list) } } -static struct lruhash_entry* +struct lruhash_entry* bin_find_entry(struct lruhash* table, struct lruhash_bin* bin, hashvalue_t hash, void* key) { @@ -293,7 +209,7 @@ bin_find_entry(struct lruhash* table, return NULL; } -static void +void table_grow(struct lruhash* table) { struct lruhash_bin* newa; @@ -329,7 +245,7 @@ table_grow(struct lruhash* table) return; } -static void +void lru_front(struct lruhash* table, struct lruhash_entry* entry) { entry->lru_prev = NULL; @@ -340,7 +256,7 @@ lru_front(struct lruhash* table, struct lruhash_entry* entry) table->lru_start = entry; } -static void +void lru_remove(struct lruhash* table, struct lruhash_entry* entry) { if(entry->lru_prev) @@ -351,7 +267,7 @@ lru_remove(struct lruhash* table, struct lruhash_entry* entry) else table->lru_end = entry->lru_prev; } -static void +void lru_touch(struct lruhash* table, struct lruhash_entry* entry) { log_assert(table && entry); diff --git a/util/storage/lruhash.h b/util/storage/lruhash.h index f4397bac7..548ca1c9b 100644 --- a/util/storage/lruhash.h +++ b/util/storage/lruhash.h @@ -266,6 +266,10 @@ void lruhash_insert(struct lruhash* table, hashvalue_t hash, struct lruhash_entry* lruhash_lookup(struct lruhash* table, hashvalue_t hash, void* key, int wr); + +/************************* Internal functions ************************/ +/*** these are only exposed for unit tests. ***/ + /** * Remove entry from hashtable. Does nothing if not found in hashtable. * Delfunc is called for the entry. @@ -275,4 +279,85 @@ struct lruhash_entry* lruhash_lookup(struct lruhash* table, hashvalue_t hash, */ void lruhash_remove(struct lruhash* table, hashvalue_t hash, void* key); +/** init the hash bins for the table. */ +void bin_init(struct lruhash_bin* array, size_t size); + +/** delete the hash bin and entries inside it */ +void bin_delete(struct lruhash* table, struct lruhash_bin* bin); + +/** + * Find entry in hash bin. You must have locked the bin. + * @param table: hash table with function pointers. + * @param bin: hash bin to look into. + * @param hash: hash value to look for. + * @param key: key to look for. + * @return: the entry or NULL if not found. + */ +struct lruhash_entry* bin_find_entry(struct lruhash* table, + struct lruhash_bin* bin, hashvalue_t hash, void* key); + +/** + * Remove entry from bin overflow chain. + * You must have locked the bin. + * @param bin: hash bin to look into. + * @param entry: entry ptr that needs removal. + */ +void bin_overflow_remove(struct lruhash_bin* bin, + struct lruhash_entry* entry); + +/** + * Split hash bin into two new ones. Based on increased size_mask. + * Caller must hold hash table lock. + * At the end the routine acquires all hashbin locks (in the old array). + * This makes it wait for other threads to finish with the bins. + * So the bins are ready to be deleted after this function. + * @param table: hash table with function pointers. + * @param newa: new increased array. + * @param newmask: new lookup mask. + */ +void bin_split(struct lruhash* table, struct lruhash_bin* newa, + int newmask); + +/** + * Try to make space available by deleting old entries. + * Assumes that the lock on the hashtable is being held by caller. + * Caller must not hold bin locks. + * @param table: hash table. + * @param list: list of entries that are to be deleted later. + * Entries have been removed from the hash table and writelock is held. + */ +void reclaim_space(struct lruhash* table, struct lruhash_entry** list); + +/** + * Grow the table lookup array. Becomes twice as large. + * Caller must hold the hash table lock. Must not hold any bin locks. + * Tries to grow, on malloc failure, nothing happened. + * @param table: hash table. + */ +void table_grow(struct lruhash* table); + +/** + * Touch entry, so it becomes the most recently used in the LRU list. + * Caller must hold hash table lock. The entry must be inserted already. + * @param table: hash table. + * @param entry: entry to make first in LRU. + */ +void lru_touch(struct lruhash* table, struct lruhash_entry* entry); + +/** + * Put entry at front of lru. entry must be unlinked from lru. + * Caller must hold hash table lock. + * @param table: hash table with lru head and tail. + * @param entry: entry to make most recently used. + */ +void lru_front(struct lruhash* table, struct lruhash_entry* entry); + +/** + * Remove entry from lru list. + * Caller must hold hash table lock. + * @param table: hash table with lru head and tail. + * @param entry: entry to remove from lru. + */ +void lru_remove(struct lruhash* table, struct lruhash_entry* entry); + #endif /* UTIL_STORAGE_LRUHASH_H */