From: Marek Vavruša Date: Sun, 29 Mar 2015 21:17:52 +0000 (+0200) Subject: lib/generic: added k-v storage to crit-bit, cleanup X-Git-Tag: v1.0.0-beta1~274^2~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=abbd5e58d540664b8090fbc7da79f78e2a42a375;p=thirdparty%2Fknot-resolver.git lib/generic: added k-v storage to crit-bit, cleanup interface split to ‘map’ and ‘set’ added documentation array: added array_tail() operation --- diff --git a/config.mk b/config.mk index 4ed2da85c..004fced6a 100644 --- a/config.mk +++ b/config.mk @@ -12,7 +12,7 @@ MODULEDIR := $(LIBDIR)/kdns_modules # Tools CC ?= cc -CFLAGS += -std=c99 -D_GNU_SOURCE -Wall -fPIC -I$(abspath .) +CFLAGS += -std=c99 -D_GNU_SOURCE -Wall -fPIC -I$(abspath .) -I$(abspath lib/generic) CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\"" RM := rm -f LN := ln -s diff --git a/lib/generic/README.rst b/lib/generic/README.rst index 50e1dec2d..7e4d765b1 100644 --- a/lib/generic/README.rst +++ b/lib/generic/README.rst @@ -9,10 +9,14 @@ as long as it comes with a test case in `tests/test_generics.c`. Data structures ~~~~~~~~~~~~~~~ -- Dynamic arrays +* ``array`` - a set of simple macros to make working with dynamic arrays easier. +* ``set`` - a `Crit-bit tree`_ simple implementation (public domain) that comes with tests. +* ``map`` - key-value map implemented on top of ``set``. API reference and examples ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. doxygengroup:: generics :project: libkresolve + +.. _`Crit-bit tree`: http://cr.yp.to/critbit.html \ No newline at end of file diff --git a/lib/generic/array.h b/lib/generic/array.h index 553e85aa4..c4191357a 100644 --- a/lib/generic/array.h +++ b/lib/generic/array.h @@ -101,6 +101,13 @@ * @return 0 on success, <0 on failure */ #define array_del(array, i) \ - (i) < (array).len ? ((array).at[i] = (array).at[(array).len], (array).len -= 1, 0) : -1 + (i) < (array).len ? ((array).len -= 1,(array).at[i] = (array).at[(array).len], 0) : -1 + +/** + * Return last element of the array. + * @warning Undefined if the array is empty. + */ +#define array_tail(array) \ + (array).at[(array).len - 1] /** @} */ diff --git a/lib/generic/critbit.h b/lib/generic/critbit.h deleted file mode 100644 index c3b921005..000000000 --- a/lib/generic/critbit.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * critbit89 - A crit-bit tree implementation for strings in C89 - * Written by Jonas Gehring - */ - - -#ifndef CRITBIT_H_ -#define CRITBIT_H_ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -/*! Main data structure */ -typedef struct { - void *root; - void *(*malloc)(size_t size, void *baton); - void (*free)(void *ptr, void *baton); - void *baton; /*! Passed to malloc() and free() */ -} cb_tree_t; - -/*! Creates an new, empty critbit tree */ -extern cb_tree_t cb_tree_make(); - -/*! Returns non-zero if tree contains str */ -extern int cb_tree_contains(cb_tree_t *tree, const char *str); - -/*! Inserts str into tree, returns 0 on suceess */ -extern int cb_tree_insert(cb_tree_t *tree, const char *str); - -/*! Deletes str from the tree, returns 0 on suceess */ -extern int cb_tree_delete(cb_tree_t *tree, const char *str); - -/*! Clears the given tree */ -extern void cb_tree_clear(cb_tree_t *tree); - -/*! Calls callback for all strings in tree with the given prefix */ -extern int cb_tree_walk_prefixed(cb_tree_t *tree, const char *prefix, - int (*callback)(const char *, void *), void *baton); - - -#ifdef __cplusplus -} -#endif - -#endif /* CRITBIT_H_ */ \ No newline at end of file diff --git a/lib/generic/critbit.c b/lib/generic/map.c similarity index 50% rename from lib/generic/critbit.c rename to lib/generic/map.c index 15fe64317..3da5149cb 100644 --- a/lib/generic/critbit.c +++ b/lib/generic/map.c @@ -1,20 +1,21 @@ /* * critbit89 - A crit-bit tree implementation for strings in C89 * Written by Jonas Gehring - */ - -/* + * Implemented key-value storing by Marek Vavrusa + * * The code makes the assumption that malloc returns pointers aligned at at * least a two-byte boundary. Since the C standard requires that malloc return * pointers that can store any type, there are no commonly-used toolchains for * which this assumption is false. + * + * See https://github.com/agl/critbit/blob/master/critbit.pdf for reference. */ #include #include #include -#include "critbit.h" +#include "map.h" #ifdef _MSC_VER /* MSVC */ typedef unsigned __int8 uint8_t; @@ -28,6 +29,10 @@ #include #endif +typedef struct { + void* value; + uint8_t key[]; +} cb_data_t; typedef struct { void *child[2]; @@ -35,6 +40,18 @@ typedef struct { uint8_t otherbits; } cb_node_t; +/* Return true if ptr is internal node. */ +static inline int ref_is_internal(uint8_t *p) +{ + return 1 & (intptr_t)p; +} + +/* Get internal node. */ +static inline cb_node_t *ref_get_internal(uint8_t *p) +{ + return (cb_node_t *)(p - 1); +} + /* Standard memory allocation functions */ static void *malloc_std(size_t size, void *baton) { (void)baton; /* Prevent compiler warnings */ @@ -47,24 +64,27 @@ static void free_std(void *ptr, void *baton) { } /* Static helper functions */ -static void cbt_traverse_delete(cb_tree_t *tree, void *top) +static void cbt_traverse_delete(map_t *map, void *top) { uint8_t *p = top; - if (1 & (intptr_t)p) { - cb_node_t *q = (void *)(p - 1); - cbt_traverse_delete(tree, q->child[0]); - cbt_traverse_delete(tree, q->child[1]); - tree->free(q, tree->baton); + if (ref_is_internal(p)) { + cb_node_t *q = ref_get_internal(p); + cbt_traverse_delete(map, q->child[0]); + cbt_traverse_delete(map, q->child[1]); + map->free(q, map->baton); } else { - tree->free(p, tree->baton); + map->free(p, map->baton); } } -static int cbt_traverse_prefixed(uint8_t *top, +static int cbt_traverse_prefixed(void *top, int (*callback)(const char *, void *), void *baton) { - if (1 & (intptr_t)top) { - cb_node_t *q = (void *)(top - 1); + uint8_t *p = top; + cb_data_t *x = (cb_data_t *)top; + + if (ref_is_internal(p)) { + cb_node_t *q = ref_get_internal(p); int ret = 0; ret = cbt_traverse_prefixed(q->child[0], callback, baton); @@ -78,34 +98,49 @@ static int cbt_traverse_prefixed(uint8_t *top, return 0; } - return (callback)((const char *)top, baton); + return (callback)((const char *)x->key, baton); } +static cb_data_t *cbt_make_data(map_t *map, const uint8_t *str, size_t len, void *value) +{ + cb_data_t *x = map->malloc(sizeof(cb_data_t) + len, map->baton); + if (x != NULL) { + x->value = value; + memcpy(x->key, str, len); + } + return x; +} -/*! Creates a new, empty critbit tree */ -cb_tree_t cb_tree_make() +/*! Creates a new, empty critbit map */ +map_t map_make(void) { - cb_tree_t tree; - tree.root = NULL; - tree.malloc = &malloc_std; - tree.free = &free_std; - tree.baton = NULL; - return tree; + map_t map; + map.root = NULL; + map.malloc = &malloc_std; + map.free = &free_std; + map.baton = NULL; + return map; } -/*! Returns non-zero if tree contains str */ -int cb_tree_contains(cb_tree_t *tree, const char *str) +/*! Returns non-zero if map contains str */ +int map_contains(map_t *map, const char *str) +{ + return map_get(map, str) != NULL; +} + +void *map_get(map_t *map, const char *str) { const uint8_t *ubytes = (void *)str; const size_t ulen = strlen(str); - uint8_t *p = tree->root; + uint8_t *p = map->root; + cb_data_t *x = NULL; if (p == NULL) { - return 0; + return NULL; } - while (1 & (intptr_t)p) { - cb_node_t *q = (void *)(p - 1); + while (ref_is_internal(p)) { + cb_node_t *q = ref_get_internal(p); uint8_t c = 0; int direction; @@ -117,34 +152,38 @@ int cb_tree_contains(cb_tree_t *tree, const char *str) p = q->child[direction]; } - return (strcmp(str, (const char *)p) == 0); + x = (cb_data_t *)p; + if (strcmp(str, (const char *)x->key) == 0) { + return x->value; + } + + return NULL; } -/*! Inserts str into tree, returns 0 on success */ -int cb_tree_insert(cb_tree_t *tree, const char *str) +/*! Inserts str into map, returns 0 on success */ +int map_set(map_t *map, const char *str, void *value) { const uint8_t *const ubytes = (void *)str; const size_t ulen = strlen(str); - uint8_t *p = tree->root; - uint8_t c, *x; - uint32_t newbyte; - uint32_t newotherbits; - int direction, newdirection; - cb_node_t *newnode; - void **wherep; + uint8_t *p = map->root; + uint8_t c = 0, *x = NULL; + uint32_t newbyte = 0; + uint32_t newotherbits = 0; + int direction = 0, newdirection = 0; + cb_node_t *newnode = NULL; + cb_data_t *data = NULL; + void **wherep = NULL; if (p == NULL) { - x = tree->malloc(ulen + 1, tree->baton); - if (x == NULL) { + map->root = cbt_make_data(map, (const uint8_t *)str, ulen + 1, value); + if (map->root == NULL) { return ENOMEM; } - memcpy(x, str, ulen + 1); - tree->root = x; return 0; } - while (1 & (intptr_t)p) { - cb_node_t *q = (void *)(p - 1); + while (ref_is_internal(p)) { + cb_node_t *q = ref_get_internal(p); c = 0; if (q->byte < ulen) { c = ubytes[q->byte]; @@ -154,15 +193,16 @@ int cb_tree_insert(cb_tree_t *tree, const char *str) p = q->child[direction]; } + data = (cb_data_t *)p; for (newbyte = 0; newbyte < ulen; ++newbyte) { - if (p[newbyte] != ubytes[newbyte]) { - newotherbits = p[newbyte] ^ ubytes[newbyte]; + if (data->key[newbyte] != ubytes[newbyte]) { + newotherbits = data->key[newbyte] ^ ubytes[newbyte]; goto different_byte_found; } } - if (p[newbyte] != 0) { - newotherbits = p[newbyte]; + if (data->key[newbyte] != 0) { + newotherbits = data->key[newbyte]; goto different_byte_found; } return 1; @@ -172,35 +212,34 @@ different_byte_found: newotherbits |= newotherbits >> 2; newotherbits |= newotherbits >> 4; newotherbits = (newotherbits & ~(newotherbits >> 1)) ^ 255; - c = p[newbyte]; + c = data->key[newbyte]; newdirection = (1 + (newotherbits | c)) >> 8; - newnode = tree->malloc(sizeof(cb_node_t), tree->baton); + newnode = map->malloc(sizeof(cb_node_t), map->baton); if (newnode == NULL) { return ENOMEM; } - x = tree->malloc(ulen + 1, tree->baton); + x = (uint8_t *)cbt_make_data(map, ubytes, ulen + 1, value); if (x == NULL) { - tree->free(newnode, tree->baton); + map->free(newnode, map->baton); return ENOMEM; } - memcpy(x, ubytes, ulen + 1); newnode->byte = newbyte; newnode->otherbits = newotherbits; newnode->child[1 - newdirection] = x; - /* Insert into tree */ - wherep = &tree->root; + /* Insert into map */ + wherep = &map->root; for (;;) { cb_node_t *q; p = *wherep; - if (!(1 & (intptr_t)p)) { + if (!ref_is_internal(p)) { break; } - q = (void *)(p - 1); + q = ref_get_internal(p); if (q->byte > newbyte) { break; } @@ -221,25 +260,26 @@ different_byte_found: return 0; } -/*! Deletes str from the tree, returns 0 on success */ -int cb_tree_delete(cb_tree_t *tree, const char *str) +/*! Deletes str from the map, returns 0 on success */ +int map_del(map_t *map, const char *str) { const uint8_t *ubytes = (void *)str; const size_t ulen = strlen(str); - uint8_t *p = tree->root; - void **wherep = 0, **whereq = 0; - cb_node_t *q = 0; + uint8_t *p = map->root; + void **wherep = NULL, **whereq = NULL; + cb_node_t *q = NULL; + cb_data_t *data = NULL; int direction = 0; - if (tree->root == NULL) { + if (map->root == NULL) { return 1; } - wherep = &tree->root; + wherep = &map->root; - while (1 & (intptr_t)p) { + while (ref_is_internal(p)) { uint8_t c = 0; whereq = wherep; - q = (void *)(p - 1); + q = ref_get_internal(p); if (q->byte < ulen) { c = ubytes[q->byte]; @@ -249,45 +289,47 @@ int cb_tree_delete(cb_tree_t *tree, const char *str) p = *wherep; } - if (strcmp(str, (const char *)p) != 0) { + data = (cb_data_t *)p; + if (strcmp(str, (const char *)data->key) != 0) { return 1; } - tree->free(p, tree->baton); + map->free(p, map->baton); if (!whereq) { - tree->root = NULL; + map->root = NULL; return 0; } *whereq = q->child[1 - direction]; - tree->free(q, tree->baton); + map->free(q, map->baton); return 0; } -/*! Clears the given tree */ -void cb_tree_clear(cb_tree_t *tree) +/*! Clears the given map */ +void map_clear(map_t *map) { - if (tree->root) { - cbt_traverse_delete(tree, tree->root); + if (map->root) { + cbt_traverse_delete(map, map->root); } - tree->root = NULL; + map->root = NULL; } -/*! Calls callback for all strings in tree with the given prefix */ -int cb_tree_walk_prefixed(cb_tree_t *tree, const char *prefix, +/*! Calls callback for all strings in map with the given prefix */ +int map_walk_prefixed(map_t *map, const char *prefix, int (*callback)(const char *, void *), void *baton) { const uint8_t *ubytes = (void *)prefix; const size_t ulen = strlen(prefix); - uint8_t *p = tree->root; - uint8_t *top = p; + uint8_t *p = map->root; + uint8_t *top = (uint8_t *)p; + cb_data_t *data = NULL; if (p == NULL) { return 0; } - while (1 & (intptr_t)p) { - cb_node_t *q = (void *)(p - 1); + while (ref_is_internal(p)) { + cb_node_t *q = ref_get_internal(p); uint8_t c = 0; int direction; @@ -302,9 +344,9 @@ int cb_tree_walk_prefixed(cb_tree_t *tree, const char *prefix, } } - if (strlen((const char *)p) < ulen || memcmp(p, prefix, ulen) != 0) { - /* No strings match */ - return 0; + data = (cb_data_t *)p; + if (strlen((const char *)data->key) < ulen || memcmp(data->key, prefix, ulen) != 0) { + return 0; /* No strings match */ } return cbt_traverse_prefixed(top, callback, baton); diff --git a/lib/generic/map.h b/lib/generic/map.h new file mode 100644 index 000000000..42cc51f2a --- /dev/null +++ b/lib/generic/map.h @@ -0,0 +1,93 @@ +/* + * critbit89 - A crit-bit map implementation for strings in C89 + * Written by Jonas Gehring + */ + +/** + * Generics - A Crit-bit tree 'map' implementation. + * + * @warning If the user provides a custom allocator, it must return addresses aligned to 2B boundary. + * + * Example usage: + * + * map_t map = map_make(); + * + * // Custom allocator (optional) + * map.malloc = &mymalloc; + * map.baton = &mymalloc_context; + * + * // Insert keys + * if (map_set(&map, "princess") != 0 || + * map_set(&map, "prince") != 0 || + * map_set(&map, "leia") != 0) { + * fail(); + * } + * + * // Test membership + * if (map_contains(&map, "leia")) { + * success(); + * } + * + * // Prefix search + * int i = 0; + * int count(const char *s, void *n) { (*(int *)n)++; return 0; } + * if (map_walk_prefixed(map, "princ", count, &i) == 0) { + * printf("%d matches\n", i); + * } + * + * // Delete + * if (map_del(&map, "badkey") != 0) { + * fail(); // No such key + * } + * + * // Clear the map + * map_clear(&map); + * + * \addtogroup generics + * @{ + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*! Main data structure */ +typedef struct { + void *root; + void *(*malloc)(size_t size, void *baton); + void (*free)(void *ptr, void *baton); + void *baton; /*! Passed to malloc() and free() */ +} map_t; + +/*! Creates an new, empty critbit map */ +map_t map_make(void); + +/*! Returns non-zero if map contains str */ +int map_contains(map_t *map, const char *str); + +/*! Returns value if map contains str */ +void *map_get(map_t *map, const char *str); + +/*! Inserts str into map, returns 0 on suceess */ +int map_set(map_t *map, const char *str, void *val); + +/*! Deletes str from the map, returns 0 on suceess */ +int map_del(map_t *map, const char *str); + +/*! Clears the given map */ +void map_clear(map_t *map); + +/*! Calls callback for all strings in map with the given prefix */ +int map_walk_prefixed(map_t *map, const char *prefix, + int (*callback)(const char *, void *), void *baton); + + +#ifdef __cplusplus +} +#endif + +/** @} */ \ No newline at end of file diff --git a/lib/generic/set.h b/lib/generic/set.h new file mode 100644 index 000000000..e7dbc55ca --- /dev/null +++ b/lib/generic/set.h @@ -0,0 +1,82 @@ +/** + * Generics - A Crit-bit set implementation. + * + * @note The API is based on @fn map.h, see it for more examples. + * + * Example usage: + * + * set_t set = set_make(); + * + * // Insert keys + * if (set_add(&set, "princess") != 0 || + * set_add(&set, "prince") != 0 || + * set_add(&set, "leia") != 0) { + * fail(); + * } + * + * // Test membership + * if (set_contains(&set, "leia")) { + * success(); + * } + * + * // Prefix search + * int i = 0; + * int count(const char *s, void *n) { (*(int *)n)++; return 0; } + * if (set_walk_prefixed(set, "princ", count, &i) == 0) { + * printf("%d matches\n", i); + * } + * + * // Delete + * if (set_del(&set, "badkey") != 0) { + * fail(); // No such key + * } + * + * // Clear the set + * set_clear(&set); + * + * \addtogroup generics + * @{ + */ + +#pragma once + +#include +#include "map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef map_t set_t; +typedef int (set_walk_cb)(const char *, void *); + +/*! Creates an new, empty critbit set */ +#define set_make() \ + map_make() + +/*! Returns non-zero if set contains str */ +#define set_contains(set, str) \ + map_contains((set), (str)) + +/*! Inserts str into set, returns 0 on suceess */ +#define set_add(set, str) \ + map_set((set), (str), (void *)1) + +/*! Deletes str from the set, returns 0 on suceess */ +#define set_del(set, str) \ + map_del((set), (str)) + +/*! Clears the given set */ +#define set_clear(set) \ + map_clear(set) + +/*! Calls callback for all strings in set with the given prefix */ +#define set_walk_prefixed(set, prefix, callback, baton) \ + map_walk_prefixed((set), (prefix), (callback), baton) + + +#ifdef __cplusplus +} +#endif + +/** @} */ \ No newline at end of file diff --git a/lib/lib.mk b/lib/lib.mk index 2522efae0..0397f89e2 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -1,5 +1,5 @@ libkresolve_SOURCES := \ - lib/generic/critbit.c \ + lib/generic/map.c \ lib/layer/iterate.c \ lib/layer/itercache.c \ lib/utils.c \ @@ -12,7 +12,8 @@ libkresolve_SOURCES := \ libkresolve_HEADERS := \ lib/generic/array.h \ - lib/generic/critbit.h \ + lib/generic/map.h \ + lib/generic/set.h \ lib/layer.h \ lib/utils.h \ lib/nsrep.h \ diff --git a/tests.unit.mk b/tests.unit.mk index fd67c54bd..d6e09da09 100644 --- a/tests.unit.mk +++ b/tests.unit.mk @@ -3,8 +3,9 @@ # tests_BIN := \ - test_critbit \ - test_generics \ + test_set \ + test_map \ + test_array \ test_utils \ test_module \ test_rplan \ diff --git a/tests/test_generics.c b/tests/test_array.c similarity index 97% rename from tests/test_generics.c rename to tests/test_array.c index 8108cab22..aeded7117 100644 --- a/tests/test_generics.c +++ b/tests/test_array.c @@ -29,6 +29,7 @@ static void test_array(void **state) assert_int_equal(arr.len, 0); assert_int_equal(array_push(arr, 5), 0); assert_int_equal(arr.at[0], 5); + assert_int_equal(array_tail(arr), 5); array_clear(arr); /* Reserve capacity and fill. */ diff --git a/tests/test_critbit.c b/tests/test_critbit.c deleted file mode 100644 index e8e548241..000000000 --- a/tests/test_critbit.c +++ /dev/null @@ -1,280 +0,0 @@ -/* - * critbit89 - A crit-bit tree implementation for strings in C89 - * Written by Jonas Gehring - */ - - -#include -#include -#include -#include - -#include "critbit.h" - - -/* - * Sample dictionary: 100 random words from /usr/share/dict/words - * Generated using random.org: - * MAX=`wc -l < /usr/share/dict/words | tr -d " "` - * for i in `curl "http://www.random.org/integers/?num=100&min=1&max=$MAX&col=1&base=10&format=plain&rnd=new"`; do - * nl /usr/share/dict/words | grep -w $i | tr -d "0-9\t " - * done - */ -static const char *dict[] = { - "catagmatic", "prevaricator", "statoscope", "workhand", "benzamide", - "alluvia", "fanciful", "bladish", "Tarsius", "unfast", "appropriative", - "seraphically", "monkeypod", "deflectometer", "tanglesome", "zodiacal", - "physiologically", "economizer", "forcepslike", "betrumpet", - "Danization", "broadthroat", "randir", "usherette", "nephropyosis", - "hematocyanin", "chrysohermidin", "uncave", "mirksome", "podophyllum", - "siphonognathous", "indoor", "featheriness", "forwardation", - "archruler", "soricoid", "Dailamite", "carmoisin", "controllability", - "unpragmatical", "childless", "transumpt", "productive", - "thyreotoxicosis", "oversorrow", "disshadow", "osse", "roar", - "pantomnesia", "talcer", "hydrorrhoea", "Satyridae", "undetesting", - "smoothbored", "widower", "sivathere", "pendle", "saltation", - "autopelagic", "campfight", "unexplained", "Macrorhamphosus", - "absconsa", "counterflory", "interdependent", "triact", "reconcentration", - "oversharpness", "sarcoenchondroma", "superstimulate", "assessory", - "pseudepiscopacy", "telescopically", "ventriloque", "politicaster", - "Caesalpiniaceae", "inopportunity", "Helion", "uncompatible", - "cephaloclasia", "oversearch", "Mahayanistic", "quarterspace", - "bacillogenic", "hamartite", "polytheistical", "unescapableness", - "Pterophorus", "cradlemaking", "Hippoboscidae", "overindustrialize", - "perishless", "cupidity", "semilichen", "gadge", "detrimental", - "misencourage", "toparchia", "lurchingly", "apocatastasis" -}; - -static int tnum = 0; - - -/* Insertions */ -static void test_insert(cb_tree_t *tree) -{ - int dict_size = sizeof(dict) / sizeof(const char *); - int i; - - for (i = 0; i < dict_size; i++) { - if (cb_tree_insert(tree, dict[i]) != 0) { - fprintf(stderr, "Insertion failed\n"); - abort(); - } - } -} - -/* Insertion of duplicate element */ -static void test_insert_dup(cb_tree_t *tree) -{ - int dict_size = sizeof(dict) / sizeof(const char *); - int i; - - for (i = 0; i < dict_size; i++) { - if (!cb_tree_contains(tree, dict[i])) { - continue; - } - if (cb_tree_insert(tree, dict[i]) != 1) { - fprintf(stderr, "Insertion of duplicate '%s' should fail\n", dict[i]); - abort(); - } - } -} - -/* Searching */ -static void test_contains(cb_tree_t *tree) -{ - char *in; - const char *notin = "not in tree"; - - in = malloc(strlen(dict[23])+1); - strcpy(in, dict[23]); - - if (cb_tree_contains(tree, in) != 1) { - fprintf(stderr, "Tree should contain '%s'\n", in); - abort(); - } - if (cb_tree_contains(tree, notin) != 0) { - fprintf(stderr, "Tree should not contain '%s'\n", notin); - abort(); - } - if (cb_tree_contains(tree, "") != 0) { - fprintf(stderr, "Tree should not contain empty string\n"); - abort(); - } - in[strlen(in)/2] = '\0'; - if (cb_tree_contains(tree, in) != 0) { - fprintf(stderr, "Tree should not contain prefix of '%s'\n", in); - abort(); - } - - free(in); -} - -/* Count number of items */ -static int count_cb(const char *s, void *n) { (*(int *)n)++; return 0; } -static void test_complete(cb_tree_t *tree, int n) -{ - int i = 0; - if (cb_tree_walk_prefixed(tree, "", count_cb, &i) != 0) { - fprintf(stderr, "Walking with empty prefix failed\n"); - abort(); - } - if (i != n) { - fprintf(stderr, "%d items expected, but %d walked\n", n, i); - abort(); - } -} - -/* Deletion */ -static void test_delete(cb_tree_t *tree) -{ - if (cb_tree_delete(tree, dict[91]) != 0) { - fprintf(stderr, "Deletion failed\n"); - abort(); - } - if (cb_tree_delete(tree, "most likely not in tree") != 1) { - fprintf(stderr, "Deletion of item not in tree should fail\n"); - abort(); - } -} - -/* Complete deletion */ -static void test_delete_all(cb_tree_t *tree) -{ - int dict_size = sizeof(dict) / sizeof(const char *); - int i; - - for (i = 0; i < dict_size; i++) { - if (!cb_tree_contains(tree, dict[i])) { - continue; - } - if (cb_tree_delete(tree, dict[i]) != 0) { - fprintf(stderr, "Deletion of '%s' failed\n", dict[i]); - abort(); - } - } -} - -/* Fake allocator */ -static void *fake_malloc(size_t s, void *b) { return NULL; } -static void test_allocator(cb_tree_t *unused) -{ - cb_tree_t tree = cb_tree_make(); - tree.malloc = fake_malloc; - if (cb_tree_insert(&tree, dict[0]) != ENOMEM) { - fprintf(stderr, "ENOMEM failure expected\n"); - abort(); - } -} - -/* Empty tree */ -static void test_empty(cb_tree_t *tree) -{ - if (cb_tree_contains(tree, dict[1]) != 0) { - fprintf(stderr, "Empty tree expected\n"); - abort(); - } - if (cb_tree_delete(tree, dict[1]) == 0) { - fprintf(stderr, "Empty tree expected\n"); - abort(); - } -} - -/* Prefix walking */ -static void test_prefixes(cb_tree_t *tree) -{ - int i = 0; - if ((cb_tree_insert(tree, "1str") != 0) || - (cb_tree_insert(tree, "11str2") != 0) || - (cb_tree_insert(tree, "12str") != 0) || - (cb_tree_insert(tree, "11str") != 0)) { - fprintf(stderr, "Insertion failed\n"); - abort(); - } - - if (cb_tree_walk_prefixed(tree, "11", count_cb, &i) != 0) { - fprintf(stderr, "Walking with prefix failed\n"); - abort(); - } - if (i != 2) { - fprintf(stderr, "2 items expected, but %d walked\n", i); - abort(); - } - - i = 0; - if (cb_tree_walk_prefixed(tree, "13", count_cb, &i) != 0) { - fprintf(stderr, "Walking with non-matching prefix failed\n"); - abort(); - } - if (i != 0) { - fprintf(stderr, "0 items expected, but %d walked\n", i); - abort(); - } - - i = 0; - if (cb_tree_walk_prefixed(tree, "12345678", count_cb, &i) != 0) { - fprintf(stderr, "Walking with long prefix failed\n"); - abort(); - } - if (i != 0) { - fprintf(stderr, "0 items expected, but %d walked\n", i); - abort(); - } - - i = 0; - if (cb_tree_walk_prefixed(tree, "11str", count_cb, &i) != 0) { - fprintf(stderr, "Walking with exactly matching prefix failed\n"); - abort(); - } - if (i != 2) { - fprintf(stderr, "2 items expected, but %d walked\n", i); - abort(); - } -} - - -/* Program entry point */ -int main(int argc, char **argv) -{ - cb_tree_t tree = cb_tree_make(); - - printf("%d ", ++tnum); fflush(stdout); - test_insert(&tree); - - printf("%d ", ++tnum); fflush(stdout); - test_complete(&tree, sizeof(dict) / sizeof(const char *)); - - printf("%d ", ++tnum); fflush(stdout); - test_insert_dup(&tree); - - printf("%d ", ++tnum); fflush(stdout); - test_contains(&tree); - - printf("%d ", ++tnum); fflush(stdout); - test_delete(&tree); - - printf("%d ", ++tnum); fflush(stdout); - cb_tree_clear(&tree); - test_insert(&tree); - test_complete(&tree, sizeof(dict) / sizeof(const char *)); - - printf("%d ", ++tnum); fflush(stdout); - test_delete_all(&tree); - - printf("%d ", ++tnum); fflush(stdout); - test_complete(&tree, 0); - - printf("%d ", ++tnum); fflush(stdout); - test_allocator(&tree); - - printf("%d ", ++tnum); fflush(stdout); - cb_tree_clear(&tree); - test_empty(&tree); - - printf("%d ", ++tnum); fflush(stdout); - test_insert(&tree); - test_prefixes(&tree); - - cb_tree_clear(&tree); - printf("ok\n"); - return 0; -} \ No newline at end of file diff --git a/tests/test_map.c b/tests/test_map.c new file mode 100644 index 000000000..0bb94031b --- /dev/null +++ b/tests/test_map.c @@ -0,0 +1,119 @@ +/* Copyright (C) 2014 CZ.NIC, z.s.p.o. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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, see . + */ + +#include +#include +#include +#include + +#include "tests/test.h" +#include "lib/generic/map.h" + +/* + * Sample dictionary + */ +static const char *dict[] = { + "catagmatic", "prevaricator", "statoscope", "workhand", "benzamide", + "alluvia", "fanciful", "bladish", "Tarsius", "unfast", "appropriative", + "seraphically", "monkeypod", "deflectometer", "tanglesome", "zodiacal", + "physiologically", "economizer", "forcepslike", "betrumpet", + "Danization", "broadthroat", "randir", "usherette", "nephropyosis", + "hematocyanin", "chrysohermidin", "uncave", "mirksome", "podophyllum", + "siphonognathous", "indoor", "featheriness", "forwardation", + "archruler", "soricoid", "Dailamite", "carmoisin", "controllability", + "unpragmatical", "childless", "transumpt", "productive", + "thyreotoxicosis", "oversorrow", "disshadow", "osse", "roar", + "pantomnesia", "talcer", "hydrorrhoea", "Satyridae", "undetesting", + "smoothbored", "widower", "sivathere", "pendle", "saltation", + "autopelagic", "campfight", "unexplained", "Macrorhamphosus", + "absconsa", "counterflory", "interdependent", "triact", "reconcentration", + "oversharpness", "sarcoenchondroma", "superstimulate", "assessory", + "pseudepiscopacy", "telescopically", "ventriloque", "politicaster", + "Caesalpiniaceae", "inopportunity", "Helion", "uncompatible", + "cephaloclasia", "oversearch", "Mahayanistic", "quarterspace", + "bacillogenic", "hamartite", "polytheistical", "unescapableness", + "Pterophorus", "cradlemaking", "Hippoboscidae", "overindustrialize", + "perishless", "cupidity", "semilichen", "gadge", "detrimental", + "misencourage", "toparchia", "lurchingly", "apocatastasis" +}; + +/* Insertions */ +static void test_insert(void **state) +{ + map_t *tree = *state; + int dict_size = sizeof(dict) / sizeof(const char *); + int i; + + for (i = 0; i < dict_size; i++) { + assert_int_equal(map_set(tree, dict[i], (void *)dict[i]), 0); + } +} + +/* Searching */ +static void test_get(void **state) +{ + map_t *tree = *state; + char *in; + const char *notin = "not in tree"; + + in = malloc(strlen(dict[23])+1); + strcpy(in, dict[23]); + + assert_int_equal(map_get(tree, in), dict[23]); + assert_int_equal(map_get(tree, notin), NULL); + assert_int_equal(map_get(tree, ""), NULL); + in[strlen(in)/2] = '\0'; + assert_int_equal(map_get(tree, in), NULL); + + free(in); +} + +/* Deletion */ +static void test_delete(void **state) +{ + map_t *tree = *state; + assert_int_equal(map_del(tree, dict[91]), 0); + assert_false(map_contains(tree, dict[91])); + assert_int_equal(map_del(tree, "most likely not in tree"), 1); +} + +static void test_init(void **state) +{ + static map_t tree; + tree = map_make(); + *state = &tree; + assert_non_null(*state); +} + +static void test_deinit(void **state) +{ + map_t *tree = *state; + map_clear(tree); +} + +/* Program entry point */ +int main(int argc, char **argv) +{ + const UnitTest tests[] = { + group_test_setup(test_init), + unit_test(test_insert), + unit_test(test_get), + unit_test(test_delete), + group_test_teardown(test_deinit) + }; + + return run_group_tests(tests); +} \ No newline at end of file diff --git a/tests/test_set.c b/tests/test_set.c new file mode 100644 index 000000000..e2e624dbc --- /dev/null +++ b/tests/test_set.c @@ -0,0 +1,219 @@ +/* + * critbit89 - A crit-bit tree implementation for strings in C89 + * Written by Jonas Gehring + */ + + +#include +#include +#include +#include + +#include "tests/test.h" +#include "lib/generic/set.h" + + +/* + * Sample dictionary: 100 random words from /usr/share/dict/words + * Generated using random.org: + * MAX=`wc -l < /usr/share/dict/words | tr -d " "` + * for i in `curl "http://www.random.org/integers/?num=100&min=1&max=$MAX&col=1&base=10&format=plain&rnd=new"`; do + * nl /usr/share/dict/words | grep -w $i | tr -d "0-9\t " + * done + */ +static const char *dict[] = { + "catagmatic", "prevaricator", "statoscope", "workhand", "benzamide", + "alluvia", "fanciful", "bladish", "Tarsius", "unfast", "appropriative", + "seraphically", "monkeypod", "deflectometer", "tanglesome", "zodiacal", + "physiologically", "economizer", "forcepslike", "betrumpet", + "Danization", "broadthroat", "randir", "usherette", "nephropyosis", + "hematocyanin", "chrysohermidin", "uncave", "mirksome", "podophyllum", + "siphonognathous", "indoor", "featheriness", "forwardation", + "archruler", "soricoid", "Dailamite", "carmoisin", "controllability", + "unpragmatical", "childless", "transumpt", "productive", + "thyreotoxicosis", "oversorrow", "disshadow", "osse", "roar", + "pantomnesia", "talcer", "hydrorrhoea", "Satyridae", "undetesting", + "smoothbored", "widower", "sivathere", "pendle", "saltation", + "autopelagic", "campfight", "unexplained", "Macrorhamphosus", + "absconsa", "counterflory", "interdependent", "triact", "reconcentration", + "oversharpness", "sarcoenchondroma", "superstimulate", "assessory", + "pseudepiscopacy", "telescopically", "ventriloque", "politicaster", + "Caesalpiniaceae", "inopportunity", "Helion", "uncompatible", + "cephaloclasia", "oversearch", "Mahayanistic", "quarterspace", + "bacillogenic", "hamartite", "polytheistical", "unescapableness", + "Pterophorus", "cradlemaking", "Hippoboscidae", "overindustrialize", + "perishless", "cupidity", "semilichen", "gadge", "detrimental", + "misencourage", "toparchia", "lurchingly", "apocatastasis" +}; + +/* Insertions */ +static void test_insert(void **state) +{ + set_t *set = *state; + int dict_size = sizeof(dict) / sizeof(const char *); + int i; + + for (i = 0; i < dict_size; i++) { + assert_int_equal(set_add(set, dict[i]), 0); + } +} + +/* Insertion of duplicate element */ +static void test_insert_dup(void **state) +{ + set_t *set = *state; + int dict_size = sizeof(dict) / sizeof(const char *); + int i; + + for (i = 0; i < dict_size; i++) { + if (!set_contains(set, dict[i])) { + continue; + } + assert_int_equal(set_add(set, dict[i]), 1); + } +} + +/* Searching */ +static void test_contains(void **state) +{ + set_t *set = *state; + char *in; + const char *notin = "not in set"; + + in = malloc(strlen(dict[23])+1); + strcpy(in, dict[23]); + + assert_true(set_contains(set, in)); + assert_false(set_contains(set, notin)); + assert_false(set_contains(set, "")); + in[strlen(in)/2] = '\0'; + assert_false(set_contains(set, in)); + + free(in); +} + +/* Count number of items */ +static int count_cb(const char *s, void *n) { (*(int *)n)++; return 0; } +static void test_complete(set_t *set, int n) +{ + int i = 0; + if (set_walk_prefixed(set, "", count_cb, &i) != 0) { + abort(); + } + if (i != n) { + abort(); + } +} +static void test_complete_full(void **state) { test_complete(*state, sizeof(dict) / sizeof(const char *)); } +static void test_complete_zero(void **state) { test_complete(*state, 0); } + +/* Deletion */ +static void test_delete(void **state) +{ + set_t *set = *state; + assert_int_equal(set_del(set, dict[91]), 0); + assert_int_equal(set_del(set, "most likely not in set"), 1); +} + +/* Complete deletion */ +static void test_delete_all(void **state) +{ + set_t *set = *state; + int dict_size = sizeof(dict) / sizeof(const char *); + int i; + + for (i = 0; i < dict_size; i++) { + if (!set_contains(set, dict[i])) { + continue; + } + assert_int_equal(set_del(set, dict[i]), 0); + } +} + +/* Fake allocator */ +static void *fake_malloc(size_t s, void *b) { return NULL; } +static void test_allocator(void **state) +{ + set_t set = set_make(); + set.malloc = fake_malloc; + assert_int_equal(set_add(&set, dict[0]), ENOMEM); +} + +/* Empty set */ +static void test_empty(void **state) +{ + set_t *set = *state; + assert_int_equal(set_contains(set, dict[1]), 0); + assert_int_not_equal(set_del(set, dict[1]), 0); +} + +/* Prefix walking */ +static void test_prefixes(void **state) +{ + set_t *set = *state; + int i = 0; + if ((set_add(set, "1str") != 0) || + (set_add(set, "11str2") != 0) || + (set_add(set, "12str") != 0) || + (set_add(set, "11str") != 0)) { + assert_int_equal(1, 0); + } + + assert_int_equal(set_walk_prefixed(set, "11", count_cb, &i), 0); + assert_int_equal(i, 2); + i = 0; + assert_int_equal(set_walk_prefixed(set, "13", count_cb, &i), 0); + assert_int_equal(i, 0); + i = 0; + assert_int_equal(set_walk_prefixed(set, "12345678", count_cb, &i), 0); + assert_int_equal(i, 0); + i = 0; + assert_int_equal(set_walk_prefixed(set, "11str", count_cb, &i), 0); + assert_int_equal(i, 2); +} + +static void test_clear(void **state) +{ + set_t *set = *state; + set_clear(set); +} + +static void test_init(void **state) +{ + static set_t set; + set = set_make(); + *state = &set; + assert_non_null(*state); +} + +static void test_deinit(void **state) +{ + set_t *set = *state; + set_clear(set); +} + +/* Program entry point */ +int main(int argc, char **argv) +{ + const UnitTest tests[] = { + group_test_setup(test_init), + unit_test(test_insert), + unit_test(test_complete_full), + unit_test(test_insert_dup), + unit_test(test_contains), + unit_test(test_delete), + unit_test(test_clear), + unit_test(test_insert), + unit_test(test_complete_full), + unit_test(test_delete_all), + unit_test(test_complete_zero), + unit_test(test_allocator), + unit_test(test_clear), + unit_test(test_empty), + unit_test(test_insert), + unit_test(test_prefixes), + group_test_teardown(test_deinit) + }; + + return run_group_tests(tests); +} \ No newline at end of file