]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
lib/generic: added k-v storage to crit-bit, cleanup
authorMarek Vavruša <marek.vavrusa@nic.cz>
Sun, 29 Mar 2015 21:17:52 +0000 (23:17 +0200)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Sun, 29 Mar 2015 21:17:52 +0000 (23:17 +0200)
interface split to ‘map’ and ‘set’
added documentation
array: added array_tail() operation

13 files changed:
config.mk
lib/generic/README.rst
lib/generic/array.h
lib/generic/critbit.h [deleted file]
lib/generic/map.c [moved from lib/generic/critbit.c with 50% similarity]
lib/generic/map.h [new file with mode: 0644]
lib/generic/set.h [new file with mode: 0644]
lib/lib.mk
tests.unit.mk
tests/test_array.c [moved from tests/test_generics.c with 97% similarity]
tests/test_critbit.c [deleted file]
tests/test_map.c [new file with mode: 0644]
tests/test_set.c [new file with mode: 0644]

index 4ed2da85ceda02dd3ad56554b22fa8f3b1eb2a11..004fced6a3b3d5db5217fe264ff9692bfdc1503e 100644 (file)
--- 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
index 50e1dec2d738296ce5610e49870a176a0159eb85..7e4d765b1ad4229284ef01a90409065211bab72b 100644 (file)
@@ -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
index 553e85aa4672ed386ca8367e7802d3a6dff9e7de..c4191357ab643e9beb49a9449ba31b52d663703d 100644 (file)
  * @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 (file)
index c3b9210..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * critbit89 - A crit-bit tree implementation for strings in C89
- * Written by Jonas Gehring <jonas@jgehring.net>
- */
-
-
-#ifndef CRITBIT_H_
-#define CRITBIT_H_
-
-#include <stddef.h>
-
-#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
similarity index 50%
rename from lib/generic/critbit.c
rename to lib/generic/map.c
index 15fe6431778ab156de3aa310a4cecfaa7a02868c..3da5149cbca205493f071aa48426807fd532a5d6 100644 (file)
@@ -1,20 +1,21 @@
 /*
  * critbit89 - A crit-bit tree implementation for strings in C89
  * Written by Jonas Gehring <jonas@jgehring.net>
- */
-
-/*
+ * Implemented key-value storing by Marek Vavrusa <marek.vavrusa@nic.cz>
+ *
  * 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 <errno.h>
 #include <string.h>
 #include <stdlib.h>
 
-#include "critbit.h"
+#include "map.h"
 
 #ifdef _MSC_VER /* MSVC */
  typedef unsigned __int8 uint8_t;
  #include <stdint.h>
 #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 (file)
index 0000000..42cc51f
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * critbit89 - A crit-bit map implementation for strings in C89
+ * Written by Jonas Gehring <jonas@jgehring.net>
+ */
+
+/**
+ * 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 <stddef.h>
+
+#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 (file)
index 0000000..e7dbc55
--- /dev/null
@@ -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 <stddef.h>
+#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
index 2522efae066a7f319431c3cf848f4ef5111c9119..0397f89e2ab42f866e8387b21ea6c23137835ef2 100644 (file)
@@ -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            \
index fd67c54bd651f6698d6fbbd53354820d43ad2f34..d6e09da09444103998632b8e76d408dc06408b65 100644 (file)
@@ -3,8 +3,9 @@
 #
 
 tests_BIN := \
-       test_critbit \
-       test_generics \
+       test_set \
+       test_map \
+       test_array \
        test_utils \
        test_module \
        test_rplan \
similarity index 97%
rename from tests/test_generics.c
rename to tests/test_array.c
index 8108cab221296212971dd3201e06b401c35c8e10..aeded71179df04d1bb7cd0b336cd780beeda1124 100644 (file)
@@ -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 (file)
index e8e5482..0000000
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * critbit89 - A crit-bit tree implementation for strings in C89
- * Written by Jonas Gehring <jonas@jgehring.net>
- */
-
-
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#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 (file)
index 0000000..0bb9403
--- /dev/null
@@ -0,0 +1,119 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..e2e624d
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * critbit89 - A crit-bit tree implementation for strings in C89
+ * Written by Jonas Gehring <jonas@jgehring.net>
+ */
+
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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