Commit 8f7447e4 authored by Rusty Russell's avatar Rusty Russell

Joey's btree module.

parent 5008de76
#include <string.h>
#include "config.h"
/**
* btree - Efficient sorted associative container based on B-trees.
*
* This module provides an efficient in-memory lookup container that keeps a
* set of pointers sorted using a user-defined search function.
*
* This module supports insertion, removal, lookup, and traversal using an
* iterator system. Note that insertion and removal into/from a btree will
* invalidate all iterators pointing to it (including the one passed to the
* insertion or removal function).
*
* btree currently doesn't have convenience functions for the simple tasks of
* "look up by key", "insert a key", and "remove a key". To insert or remove,
* you first have use btree_find* to position an iterator on the
* insertion/removal point, then call btree_insert_at or btree_remove_at using
* that iterator. Since a btree can hold multiple items with the same key,
* it isn't clear how the convenience functions should work yet. I'm open to
* suggestions.
*
* A B-tree (not to be confused with a binary tree) is a data structure that
* performs insertion, removal, and lookup in O(log n) time per operation.
* Although B-trees are typically used for databases and filesystems, this is
* an in-memory implementation.
*
* Unlike functions like qsort, bsearch, and tsearch, btree does not take a
* comparison function. It takes a binary search function, which is
* theoretically equivalent but faster. Writing a binary search function
* is more difficult than writing a comparison function, so a macro is provided
* to make it much easier than doing either manually.
*
* Example:
* #include <ccan/btree/btree.h>
*
* #include <errno.h>
* #include <stdlib.h>
* #include <stdio.h>
* #include <string.h>
*
* struct word {
* char *word;
* char *letter_set;
* };
*
* //Define the ordering function order_by_letter_set
* btree_search_implement
* (
* order_by_letter_set,
* struct word *,
* int c = strcmp(a->letter_set, b->letter_set),
* c == 0,
* c < 0
* )
*
* struct word *new_word(const char *line);
* char *chomp(char *str);
* char *make_letter_set(char *str);
*
* void insert_word(struct btree *btree, struct word *word)
* {
* btree_iterator iter;
*
* //Position iterator after existing words with this letter set.
* btree_find_last(btree, word, iter);
*
* //Insert new word at iterator position.
* btree_insert_at(iter, word);
* }
*
* void print_anagrams(struct btree *btree, char *line)
* {
* btree_iterator iter, end;
* struct word key = {
* NULL,
* make_letter_set(line)
* };
*
* //Find first matching word.
* if (!btree_find_first(btree, &key, iter)) {
* printf("\t(none)\n");
* return;
* }
*
* btree_find_last(btree, &key, end);
*
* //Traverse matching words.
* for (; btree_deref(iter) && btree_cmp_iters(iter, end) < 0;
* btree_next(iter))
* {
* struct word *word = iter->item;
* printf("\t%s\n", word->word);
* }
* }
*
* int destroy_word(struct word *word, void *ctx)
* {
* (void) ctx;
*
* free(word->word);
* free(word->letter_set);
* free(word);
*
* return 1;
* }
*
* struct btree *read_dictionary(const char *filename)
* {
* FILE *f;
* char line[256];
*
* //Construct new btree with the ordering function order_by_letter_set .
* struct btree *btree = btree_new(order_by_letter_set);
*
* f = fopen(filename, "r");
* if (!f)
* goto fail;
*
* //Set the destroy callback so btree_free will automatically
* //free our items. Setting btree->destroy is optional.
* btree->destroy = (btree_action_t)destroy_word;
*
* while (fgets(line, sizeof(line), f)) {
* struct word *word = new_word(line);
* if (!word)
* continue;
* insert_word(btree, word);
* }
*
* if (ferror(f)) {
* fclose(f);
* goto fail;
* }
* if (fclose(f))
* goto fail;
*
* return btree;
*
* fail:
* btree_delete(btree);
* fprintf(stderr, "%s: %s\n", filename, strerror(errno));
* return NULL;
* }
*
* int main(int argc, char *argv[])
* {
* struct btree *btree;
* char line[256];
*
* if (argc != 2) {
* fprintf(stderr,
* "Usage: %s dictionary-file\n"
* "Example:\n"
* "\t%s /usr/share/dict/words\n"
* "\n"
* , argv[0], argv[0]);
* return 1;
* }
*
* printf("Indexing...\n");
* btree = read_dictionary(argv[1]);
* printf("Dictionary has %ld words\n", (long)btree->count);
*
* for (;;) {
* printf("> ");
* if (!fgets(line, sizeof(line), stdin))
* break;
* chomp(line);
* if (!*line)
* break;
*
* printf("Anagrams of \"%s\":\n", line);
* print_anagrams(btree, line);
* }
*
* printf("Cleaning up...\n");
* btree_delete(btree);
*
* return 0;
* }
*
* struct word *new_word(const char *line)
* {
* struct word *word;
* char *letter_set = make_letter_set(strdup(line));
*
* //Discard lines with no letters
* if (!*letter_set) {
* free(letter_set);
* return NULL;
* }
*
* word = malloc(sizeof(struct word));
* word->word = chomp(strdup(line));
* word->letter_set = letter_set;
*
* return word;
* }
*
* //Remove trailing newline (if present).
* char *chomp(char *str)
* {
* char *end = strchr(str, '\0') - 1;
* if (*str && *end == '\n')
* *end = 0;
* return str;
* }
*
* //Remove all characters but letters, make letters lowercase, and sort them.
* char *make_letter_set(char *str)
* {
* size_t count[26] = {0};
* size_t i, j;
* char *o = str;
*
* for (i=0; str[i]; i++) {
* char c = str[i];
* if (c >= 'A' && c <= 'Z')
* c += 'a'-'A';
* if (c >= 'a' && c <= 'z')
* count[c - 'a']++;
* }
*
* for (i = 0; i < 26; i++) {
* for (j = 0; j < count[i]; j++)
* *o++ = 'a' + i;
* }
* *o = '\0';
*
* return str;
* }
*
* Author: Joey Adams
* Version: 0.1.0
* Licence: BSD
*/
int main(int argc, char *argv[])
{
/* Expect exactly one argument */
if (argc != 2)
return 1;
if (strcmp(argv[1], "depends") == 0) {
/* Nothing */
return 0;
}
return 1;
}
This diff is collapsed.
/*
Copyright (c) 2010 Joseph A. Adams
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*/
#ifndef CCAN_BTREE_H
#define CCAN_BTREE_H
/*
Note: The following should work but are not well-tested yet:
btree_walk...
btree_cmp_iters
*/
#include <stdint.h>
#include <stddef.h>
/*
* Maximum number of items per node.
* The maximum number of branches is BTREE_ITEM_MAX + 1.
*/
#define BTREE_ITEM_MAX 20
struct btree_node {
struct btree_node *parent;
/* Number of items (rather than branches). */
unsigned char count;
/* 0 if node is a leaf, 1 if it has leaf children, etc. */
unsigned char depth;
/* node->parent->branch[node->k] == this */
unsigned char k;
const void *item[BTREE_ITEM_MAX];
/*
* Allocated to BTREE_ITEM_MAX+1 items if this is
* an internal node, 0 items if it is a leaf.
*/
struct btree_node *branch[];
};
typedef struct btree_iterator_s {
struct btree *btree;
struct btree_node *node;
unsigned int k;
/*
* The relationship between item and (node, k) depends on what function
* set it. It is mainly for convenience.
*/
void *item;
} btree_iterator[1];
/*
* Instead of a compare function, this library accepts a binary search function
* to know how to order the items.
*/
typedef unsigned int btree_search_proto(
const void *key,
const void * const *base,
unsigned int count,
int lr,
int *found
);
typedef btree_search_proto *btree_search_t;
/*
* Callback used by btree_delete() and btree_walk...().
*
* If it returns 0, it causes btree_walk...() to stop traversing and return 0.
* Thus, in normal circumstances, this callback should return 1.
*
* Callback shall not insert/remove items from the btree being traversed,
* nor shall anything modify it during a walk.
*/
typedef int (*btree_action_t)(void *item, void *ctx);
struct btree {
struct btree_node *root;
size_t count; /* Total number of items in B-tree */
btree_search_t search;
/*
* These are set to NULL by default.
*
* When destroy is not NULL, it is called on each item in order when
* btree_delete() is called.
*
* When destroy is NULL, btree_delete runs faster because it does not have
* to visit each and every item.
*/
btree_action_t destroy;
void *destroy_ctx;
};
struct btree *btree_new(btree_search_t search);
void btree_delete(struct btree *btree);
/* lr must be 0 or 1, nothing else. */
int btree_begin_end_lr(const struct btree *btree, btree_iterator iter, int lr);
int btree_find_lr(const struct btree *btree, const void *key,
btree_iterator iter, int lr);
int btree_walk_backward(const struct btree *btree,
btree_action_t action, void *ctx);
int btree_walk_forward(const struct btree *btree,
btree_action_t action, void *ctx);
#define btree_begin(btree, iter) btree_begin_end_lr(btree, iter, 0)
#define btree_end(btree, iter) btree_begin_end_lr(btree, iter, 1)
int btree_prev(btree_iterator iter);
int btree_next(btree_iterator iter);
#define btree_walk(btree, action, ctx) btree_walk_forward(btree, action, ctx)
/*
* If key was found, btree_find_first will return 1, iter->item will be the
* first matching item, and iter will point to the beginning of the matching
* items.
*
* If key was not found, btree_find_first will return 0, iter->item will be
* undefined, and iter will point to where the key should go if inserted.
*/
#define btree_find_first(btree, key, iter) btree_find_lr(btree, key, iter, 0)
/*
* If key was found, btree_find_last will return 1, iter->item will be the
* last matching item, and iter will point to the end of the matching
* items.
*
* If key was not found, btree_find_last will return 0, iter->item will be
* undefined, and iter will point to where the key should go if inserted.
*/
#define btree_find_last(btree, key, iter) btree_find_lr(btree, key, iter, 1)
/* btree_find is an alias of btree_find_first. */
#define btree_find(btree, key, iter) btree_find_first(btree, key, iter)
/*
* If iter points to an item, btree_deref returns 1 and sets iter->item to the
* item it points to.
*
* Otherwise (if iter points to the end of the btree), btree_deref returns 0
* and leaves iter untouched.
*/
int btree_deref(btree_iterator iter);
/*
* Inserts the item before the one pointed to by iter.
*
* Insertion invalidates all iterators to the btree, including the one
* passed to btree_insert_at. Nevertheless, iter->item will be set to
* the item inserted.
*/
void btree_insert_at(btree_iterator iter, const void *item);
/*
* Removes the item pointed to by iter. Returns 1 if iter pointed
* to an item. Returns 0 if iter pointed to the end, in which case
* it leaves iter intact.
*
* Removal invalidates all iterators to the btree, including the one
* passed to btree_remove_at. Nevertheless, iter->item will be set to
* the item removed.
*/
int btree_remove_at(btree_iterator iter);
/*
* Compares positions of two iterators.
*
* Returns -1 if a is before b, 0 if a is at the same position as b,
* and +1 if a is after b.
*/
int btree_cmp_iters(const btree_iterator iter_a, const btree_iterator iter_b);
#define btree_search_implement(name, type, setup, equals, lessthan) \
unsigned int name(const void *__key, \
const void * const *__base, unsigned int __count, \
int __lr, int *__found) \
{ \
unsigned int __start = 0; \
while (__count) { \
unsigned int __middle = __count >> 1; \
type a = (type)__key; \
type b = (type)__base[__start + __middle]; \
{ \
setup; \
if (equals) \
goto __equals; \
if (lessthan) \
goto __lessthan; \
} \
__greaterthan: \
__start += __middle + 1; \
__count -= __middle + 1; \
continue; \
__equals: \
*__found = 1; \
if (__lr) \
goto __greaterthan; \
/* else, fall through to __lessthan */ \
__lessthan: \
__count = __middle; \
continue; \
} \
return __start; \
}
#endif /* #ifndef CCAN_BTREE_H */
/* Include the main header first, to test it works */
#include <ccan/btree/btree.h>
/* Include the C files directly. */
#include <ccan/btree/btree.c>
#include <ccan/tap/tap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
uint32_t rand32_state = 0;
/*
* Finds a pseudorandom 32-bit number from 0 to 2^32-1 .
* Uses the BCPL linear congruential generator method.
*/
static uint32_t rand32(void)
{
rand32_state *= (uint32_t)0x7FF8A3ED;
rand32_state += (uint32_t)0x2AA01D31;
return rand32_state;
}
static void scramble(void *base, size_t nmemb, size_t size)
{
char *i = base;
char *o;
size_t sd;
for (;nmemb>1;nmemb--) {
o = i + size*(rand32()%nmemb);
for (sd=size;sd--;) {
char tmp = *o;
*o++ = *i;
*i++ = tmp;
}
}
}
struct test_item {
size_t key;
uint32_t value;
};
/* For ordering a btree of test_item pointers. */
static btree_search_implement (
order_by_key,
const struct test_item *,
,
a == b,
a < b
)
/* For qsorting an array of test_item pointers. */
static int compare_test_item(const void *ap, const void *bp)
{
const struct test_item *a = *(const struct test_item * const*)ap;
const struct test_item *b = *(const struct test_item * const*)bp;
if (a == b)
return 0;
if (a < b)
return -1;
return 1;
}
/*
* If lr == 0, make sure iter points to the item given.
* If lr == 1, make sure iter points to after the item given.
*/
static int check_iter(btree_iterator iter_orig, const void *item, int lr)
{
btree_iterator iter = {*iter_orig};
if (iter->item != item)
return 0;
if (lr) {
if (!btree_prev(iter))
return 0;
} else {
if (!btree_deref(iter))
return 0;
}
if (iter->item != item)
return 0;
if (iter->node->item[iter->k] != iter->item)
return 0;
return 1;
}
/*
* Returns 1 on insert, 0 on duplicate,
* -1 on bad iterator returned by find, and
* -2 on bad iterator returned by insert.
*/
static int insert_test_item(struct btree *btree, struct test_item *item)
{
btree_iterator iter;
int lr;
/* Find the first or last matching item, randomly choosing between the two. */
lr = rand32() & 1;
if (btree_find_lr(btree, item, iter, lr)) {
if (!check_iter(iter, item, lr))
return -1;
return 0;
}
btree_insert_at(iter, item);
if (iter->item != item)
return -2;
return 1;
}
/*
* Returns 1 on remove, 0 on missing,
* -1 on bad iterator returned by find, and
* -2 on bad iterator returned by remove.
*/
static int remove_test_item(struct btree *btree, struct test_item *item)
{
btree_iterator iter;
if (!btree_find(btree, item, iter))
return 0;
if (!check_iter(iter, item, 0))
return -1;
btree_remove_at(iter);
if (iter->item != item)
return -2;
return 1;
}
static struct {
size_t success;
size_t excess;
size_t duplicate;
size_t missing;
size_t incorrect;
size_t failed;
size_t bad_iter_find;
size_t bad_iter_insert;
size_t bad_iter_remove;
size_t bad_iter_next;
} stats;
static void clear_stats(void) {
memset(&stats, 0, sizeof(stats));
}
static int print_stats(const char *success_label, size_t expected_success) {
int failed = 0;
printf(" %s: \t%zu\n", success_label, stats.success);
if (stats.success != expected_success)
failed = 1;
if (stats.excess)
failed = 1, printf(" Excess: \t%zu\n", stats.excess);
if (stats.duplicate)
failed = 1, printf(" Duplicate: \t%zu\n", stats.duplicate);
if (stats.missing)
failed = 1, printf(" Missing: \t%zu\n", stats.missing);
if (stats.incorrect)
failed = 1, printf(" Incorrect: \t%zu\n", stats.incorrect);
if (stats.failed)
failed = 1, printf(" Failed: \t%zu\n", stats.failed);
if (stats.bad_iter_find || stats.bad_iter_insert ||
stats.bad_iter_remove || stats.bad_iter_next) {
failed = 1;
printf(" Bad iterators yielded by:\n");
if (stats.bad_iter_find)
printf(" btree_find_lr(): %zu\n", stats.bad_iter_find);
if (stats.bad_iter_insert)
printf(" btree_insert_at(): %zu\n", stats.bad_iter_insert);
if (stats.bad_iter_remove)
printf(" btree_remove_at(): %zu\n", stats.bad_iter_remove);
if (stats.bad_iter_next)
printf(" btree_next(): %zu\n", stats.bad_iter_next);
}
return !failed;
}
static void benchmark(size_t max_per_trial, size_t round_count, int random_counts)
{
struct test_item **test_item;
struct test_item *test_item_array;
size_t i, count;
size_t round = 0;
test_item = malloc(max_per_trial * sizeof(*test_item));
test_item_array = malloc(max_per_trial * sizeof(*test_item_array));
if (!test_item || !test_item_array) {
fail("Not enough memory for %zu keys per trial\n",
max_per_trial);
return;
}
/* Initialize test_item pointers. */
for (i=0; i<max_per_trial; i++)
test_item[i] = &test_item_array[i];
/*
* If round_count is not zero, run round_count trials.
* Otherwise, run forever.
*/
for (round = 1; round_count==0 || round <= round_count; round++) {
struct btree *btree;
btree_iterator iter;
printf("Round %zu\n", round);
if (random_counts)
count = rand32() % (max_per_trial+1);
else
count = max_per_trial;
/*
* Initialize test items by giving them sequential keys and
* random values. Scramble them so the order of insertion
* will be random.
*/
for (i=0; i<count; i++) {
test_item[i]->key = i;
test_item[i]->value = rand32();
}
scramble(test_item, count, sizeof(*test_item));
btree = btree_new(order_by_key);
clear_stats();
printf(" Inserting %zu items...\n", count);
for (i=0; i<count; i++) {
switch (insert_test_item(btree, test_item[i])) {
case 1: stats.success++; break;
case 0: stats.duplicate++; break;
case -1: stats.bad_iter_find++; break;
case -2: stats.bad_iter_insert++; break;
default: stats.failed++; break;
}
}
ok1(print_stats("Inserted", count));
scramble(test_item, count, sizeof(*test_item));
printf(" Finding %zu items...\n", count);
clear_stats();
for (i=0; i<count; i++) {
int lr = rand32() & 1;
if (!btree_find_lr(btree, test_item[i], iter, lr)) {
stats.missing++;
continue;
}
if (!check_iter(iter, test_item[i], lr)) {
stats.bad_iter_find++;
continue;
}
stats.success++;
}
ok1(print_stats("Retrieved", count));
qsort(test_item, count, sizeof(*test_item), compare_test_item);
printf(" Traversing forward through %zu items...\n", count);
clear_stats();
i = 0;
for (btree_begin(btree, iter); btree_next(iter);) {
if (i >= count) {
stats.excess++;
continue;
}
if (iter->item == test_item[i])
stats.success++;
else
stats.incorrect++;
i++;
}
ok1(print_stats("Retrieved", count));
printf(" Traversing backward through %zu items...\n", count);
clear_stats();
i = count;
for (btree_end(btree, iter); btree_prev(iter);) {
if (!i) {
stats.excess++;
continue;
}
i--;
if (iter->item == test_item[i])
stats.success++;
else
stats.incorrect++;
}
ok1(print_stats("Retrieved", count));
ok1(btree->count == count);
//static int remove_test_item(struct btree *btree, struct test_item *item);
scramble(test_item, count, sizeof(*test_item));
printf(" Deleting %zu items...\n", count);
clear_stats();
for (i=0; i<count; i++) {
int s = remove_test_item(btree, test_item[i]);
if (s != 1)
printf("remove_test_item failed\n");
switch (s) {
case 1: stats.success++; break;
case 0: stats.missing++; break;
case -1: stats.bad_iter_find++; break;
case -2: stats.bad_iter_remove++; break;
default: stats.failed++; break;
}
}
ok1(btree->count == 0);
ok1(print_stats("Deleted", count));
ok1(btree->root->depth == 0 && btree->root->count == 0);
btree_delete(btree);
}
free(test_item);
free(test_item_array);
}
int main(void)
{
plan_tests(32);
benchmark(300000, 4, 0);
return exit_status();
}
/* Include the main header first, to test it works */
#include <ccan/btree/btree.h>
/* Include the C files directly. */
#include <ccan/btree/btree.c>
#include <ccan/tap/tap.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static uint32_t rand32_state = 0;
/*
* Finds a pseudorandom 32-bit number from 0 to 2^32-1 .
* Uses the BCPL linear congruential generator method.
*/
static uint32_t rand32(void)
{
rand32_state *= (uint32_t)0x7FF8A3ED;
rand32_state += (uint32_t)0x2AA01D31;
return rand32_state;
}
/*
* Whether or not to add/remove multiple equal keys to the tree.
*
* Tests are run with multi set to 0 and 1.
*/
static int multi = 0;
static struct {
struct {
size_t success;
size_t failure;
} insert, remove, find, traverse;
} stats;
static int check_stats(void) {
return
stats.insert.failure == 0 &&
stats.remove.failure == 0 &&
stats.find.failure == 0 &&
stats.traverse.failure == 0;
}
static int print_stats(void) {
printf("Insert: %zu succeeded, %zu failed\n",
stats.insert.success, stats.insert.failure);
printf("Remove: %zu succeeded, %zu failed\n",
stats.remove.success, stats.remove.failure);
printf("Find: %zu succeeded, %zu failed\n",
stats.find.success, stats.find.failure);
printf("Traverse: %zu succeeded, %zu failed\n",
stats.traverse.success, stats.traverse.failure);
return check_stats();
}
static void clear_stats(void)
{
memset(&stats, 0, sizeof(stats));
}
static int test_node_consistency(struct btree_node *node, struct btree_node *parent, size_t *count)
{
unsigned int i, j, e = node->count;
/* Verify parent, depth, and k */
if (node->parent != parent)
return 0;
if (parent) {
if (node->depth != parent->depth - 1)
return 0;
if (node != parent->branch[node->k])
return 0;
}
/* Nodes must not be empty (unless the entire tree is empty). */
if (e == 0)
return 0;
if (node->depth) {
/* Make sure child branches aren't duplicated or NULL. */
for (i=0; i<=e; i++) {
if (node->branch[i] == NULL)
return 0;
for (j=i+1; j<=e; j++)
if (node->branch[i] == node->branch[j])
return 0;
}
/* Recursively check children. */
for (i=0; i<=e; i++) {
if (!test_node_consistency(node->branch[i], node, count))
return 0;
}
}
*count += node->count;
return 1;
}
static int test_consistency(const struct btree *btree)
{
size_t count = 0;
if (!btree->root)
return 0;
if (btree->root->count == 0) {
if (btree->count != 0)
return 0;
return 1;
}
if (btree->count == 0)
return 0;
if (!test_node_consistency(btree->root, NULL, &count))
return 0;
if (btree->count != count)
return 0;
return 1;
}
static int test_traverse(struct btree *btree, size_t key[], size_t count)
{
btree_iterator iter;
size_t i, j;
if (!test_consistency(btree))
return 0;
/* Forward */
i = 0;
btree_begin(btree, iter);
for (;;) {
while (i < count && key[i] == 0)
i++;
if (i >= count) {
if (btree_next(iter))
return 0;
break;
}
for (j = 0; j < key[i] && btree_next(iter); j++) {
if (iter->item != &key[i])
return 0;
}
if (j != key[i])
return 0;
i++;
}
/* Backward */
i = count;
btree_end(btree, iter);
for (;;) {
while (i > 0 && key[i-1] == 0)
i--;
if (i <= 0) {
if (btree_prev(iter))
return 0;
break;
}
for (j = 0; j < key[i-1] && btree_prev(iter); j++) {
if (iter->item != &key[i-1])
return 0;
}
if (j != key[i-1])
return 0;
i--;
}
return 1;
}
/*
* Finds and counts items matching &key[k], then makes sure the count
* equals key[k]. Also verifies that btree_find_... work as advertised.
*/
static int find(struct btree *btree, size_t key[], size_t k)
{
btree_iterator iter, tmp;
size_t count;
int found;
memset(iter, 0, sizeof(iter));
memset(tmp, 0, sizeof(tmp));
found = btree_find_first(btree, &key[k], iter);
if (iter->btree != btree)
return 0;
if (found != !!key[k])
return 0;
if (key[k] && iter->item != &key[k])
return 0;
/* Make sure btree_find works exactly the same as btree_find_first. */
if (btree_find(btree, &key[k], tmp) != found)
return 0;
if (memcmp(iter, tmp, sizeof(*iter)))
return 0;
/* Make sure previous item doesn't match. */
*tmp = *iter;
if (btree_prev(tmp)) {
if (tmp->item == &key[k])
return 0;
}
/* Count going forward. */
for (count=0; btree_deref(iter) && iter->item == &key[k]; count++, btree_next(iter))
{}
if (count != key[k])
return 0;
/* Make sure next item doesn't match. */
*tmp = *iter;
if (btree_deref(tmp)) {
if (tmp->item == &key[k])
return 0;
}
/* Make sure iter is now equal to the end of matching items. */
btree_find_last(btree, &key[k], tmp);
if (tmp->btree != btree)
return 0;
if (btree_cmp_iters(iter, tmp))
return 0;
/* Count going backward. */
for (count=0; btree_prev(iter); count++) {
if (iter->item != &key[k]) {
btree_next(iter);
break;
}
}
if (count != key[k])
return 0;
/* Make sure iter is now equal to the beginning of matching items. */
btree_find_first(btree, &key[k], tmp);
if (tmp->btree != btree)
return 0;
if (btree_cmp_iters(iter, tmp))
return 0;
return 1;
}
static int test_find(struct btree *btree, size_t key[], size_t count)
{
size_t k = rand32() % count;
return find(btree, key, k);
}
static int test_remove(struct btree *btree, size_t key[], size_t count)
{
size_t prev_count = btree->count;
size_t k = rand32() % count;
btree_iterator iter;
if (!find(btree, key, k))
return 0;
btree_find(btree, &key[k], iter);
//remove (if present), and make sure removal status is correct
if (key[k]) {
if (btree_remove_at(iter) != 1)
return 0;
if (btree->count != prev_count - 1)
return 0;
key[k]--;
if (!find(btree, key, k))
return 0;
}
return 1;
}
static int test_insert(struct btree *btree, size_t key[], size_t count)
{
size_t k = rand32() % count;
btree_iterator iter;
size_t prev_count = btree->count;
int found;
if (!find(btree, key, k))
return 0;
/* Make sure key's presense is consistent with our array. */
found = btree_find_first(btree, &key[k], iter);
if (key[k]) {
if (!found || iter->item != &key[k])
return 0;
if (!btree_deref(iter))
return 0;
if (iter->k >= iter->node->count || iter->node->item[iter->k] != &key[k])
return 0;
} else {
if (found)
return 0;
}
/* Insert if item hasn't been yet (or if we're in multi mode). */
if (!key[k] || multi) {
btree_insert_at(iter, &key[k]);
key[k]++;
if (btree->count != prev_count + 1)
return 0;
}
/* Check result iterator's ->item field. */
if (iter->item != &key[k])
return 0;
if (!find(btree, key, k))
return 0;
/* Make sure key is present and correct now. */
found = btree_find_first(btree, &key[k], iter);
if (!found || iter->item != &key[k])
return 0;
return 1;
}
static btree_search_implement(order_by_ptr, size_t*, , a == b, a < b)
static void stress(size_t count, size_t trials)
{
struct btree *btree = btree_new(order_by_ptr);
size_t *key = calloc(count, sizeof(*key));
size_t i;
clear_stats();
for (i=0; i<trials; i++) {
unsigned int n = rand32() % 65536;
unsigned int rib = btree->count * 43688 / count;
//remove/insert boundary
if (n >= 65534) {
if (test_traverse(btree, key, count))
stats.traverse.success++;
else
stats.traverse.failure++;
} else if (n >= 46388) {
if (test_find(btree, key, count))
stats.find.success++;
else
stats.find.failure++;
} else if (n < rib) {
if (test_remove(btree, key, count))
stats.remove.success++;
else
stats.remove.failure++;
} else {
if (test_insert(btree, key, count))
stats.insert.success++;
else
stats.insert.failure++;
}
}
free(key);
btree_delete(btree);
print_stats();
ok1(check_stats());
}
int main(void)
{
plan_tests(2);
multi = 0;
printf("Running with multi = %d\n", multi);
stress(100000, 500000);
multi = 1;
printf("Running with multi = %d\n", multi);
stress(100000, 500000);
return exit_status();
}
/* Include the main header first, to test it works */
#include <ccan/btree/btree.h>
/* Include the C files directly. */
#include <ccan/btree/btree.c>
#include <ccan/tap/tap.h>
#include <string.h>
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(*(array)))
#endif
struct foo {
const char *string;
int number;
};
struct foo foo_structs[] = {
{"apple", 1},
{"banana", 2},
{"banana", 4},
{"cherry", 4},
{"doughnut", 5},
};
struct foo *foo_base[ARRAY_SIZE(foo_structs)];
const unsigned int foo_count = ARRAY_SIZE(foo_structs);
static void init_foo_pointers(void)
{
unsigned int i;
for (i = 0; i < foo_count; i++)
foo_base[i] = &foo_structs[i];
}
/* Make sure forward declarations work */
btree_search_proto order_by_string, order_by_number;
static void test_order_by_string(void)
{
struct {
const char *key;
int lr;
unsigned int expect_offset;
int expect_found;
} test[] = {
{"anchovies", 0, 0, 0},
{"anchovies", 1, 0, 0},
{"apple", 0, 0, 1},
{"apple", 1, 1, 1},
{"banana", 0, 1, 1},
{"banana", 1, 3, 1},
{"blueberry", 0, 3, 0},
{"blueberry", 1, 3, 0},
{"doughnut", 0, 4, 1},
{"doughnut", 1, 5, 1},
};
size_t i;
for (i=0; i<ARRAY_SIZE(test); i++) {
struct foo foo = {test[i].key, 0};
unsigned int offset;
int found = 0;
offset = order_by_string(&foo, (void*)foo_base, foo_count,
test[i].lr, &found);
ok1(offset == test[i].expect_offset && found == test[i].expect_found);
}
}
static void test_empty(void)
{
unsigned int offset;
int found;
struct foo key = {"apple", -1};
offset = order_by_string(&key, NULL, 0, 0, &found);
ok1(offset == 0);
offset = order_by_string(&key, NULL, 0, 1, &found);
ok1(offset == 0);
offset = order_by_number(&key, NULL, 0, 0, &found);
ok1(offset == 0);
offset = order_by_number(&key, NULL, 0, 1, &found);
ok1(offset == 0);
}
static void test_order_by_number(void)
{
struct {
int key;
int lr;
unsigned int expect_offset;
int expect_found;
} test[] = {
{-2, 0, 0, 0},
{-2, 1, 0, 0},
{-1, 0, 0, 0},
{-1, 1, 0, 0},
{0, 0, 0, 0},
{0, 1, 0, 0},
{1, 0, 0, 1},
{1, 1, 1, 1},
{2, 0, 1, 1},
{2, 1, 2, 1},
{4, 0, 2, 1},
{4, 1, 4, 1},
{3, 0, 2, 0},
{3, 1, 2, 0},
{5, 0, 4, 1},
{5, 1, 5, 1},
{6, 0, 5, 0},
{6, 1, 5, 0},
{7, 0, 5, 0},
{7, 1, 5, 0},
};
size_t i;
for (i=0; i<ARRAY_SIZE(test); i++) {
struct foo foo = {"", test[i].key};
unsigned int offset;
int found = 0;
offset = order_by_number(&foo, (void*)foo_base, foo_count,
test[i].lr, &found);
ok1(offset == test[i].expect_offset && found == test[i].expect_found);
}
}
int main(void)
{
plan_tests(34);
init_foo_pointers();
test_order_by_string();
test_order_by_number();
test_empty();
return exit_status();
}
btree_search_implement (
order_by_string,
const struct foo *,
int c = strcmp(a->string, b->string),
c == 0,
c < 0
)
btree_search_implement (
order_by_number,
const struct foo *,
,
a->number == b->number,
a->number < b->number
)
/* Include the main header first, to test it works */
#include <ccan/btree/btree.h>
/* Include the C files directly. */
#include <ccan/btree/btree.c>
#include <ccan/tap/tap.h>
#include <string.h>
struct test_item {
int key;
int value;
};
static btree_search_implement(
order_by_key,
struct test_item *,
,
a->key == b->key,
a->key < b->key
)
static int insert_test_item(struct btree *btree, int key, int value)
{
struct test_item key_item = {key, -101};
struct test_item *item;
btree_iterator iter;
if (btree_find_first(btree, &key_item, iter)) {
/* Don't insert new item, but do update its value. */
item = iter->item;
item->value = value;
return 0;
}
item = malloc(sizeof(*item));
item->key = key;
item->value = value;
btree_insert_at(iter, item);
return 1;
}
static int lookup_test_item(const struct btree *btree, int key)
{
struct test_item key_item = {key, -102};
struct test_item *item;
btree_iterator iter;
if (!btree_find_first(btree, &key_item, iter))
return -100;
item = iter->item;
return item->value;
}
static int destroy_test_item(void *item, void *ctx) {
(void) ctx;
free(item);
return 1;
}
struct test_insert_entry {
int key;
int value;
int expected_return;
};
struct test_traverse_entry {
int key;
int value;
};
static void print_indent(unsigned int indent) {
while (indent--)
fputs("\t", stdout);
}
static void btree_node_trace(struct btree_node *node, unsigned int indent)
{
unsigned int i;
for (i=0; i<node->count; i++) {
if (node->depth)
btree_node_trace(node->branch[i], indent+1);
print_indent(indent);
puts(node->item[i]);
}
if (node->depth)
btree_node_trace(node->branch[node->count], indent+1);
}
static void btree_trace(struct btree *btree)
{
btree_node_trace(btree->root, 0);
}
static void test_insert(struct btree *btree)
{
struct test_insert_entry ent[] = {
{3, 1, 1}, {4, 1, 1}, {5, 9, 1}, {2, 6, 1}, {5, 3, 0}, {5, 8, 0},
{9, 7, 1}, {9, 3, 0}, {2, 3, 0}, {8, 4, 1}, {6, 2, 1}, {6, 4, 0},
{3, 3, 0}, {8, 3, 0}, {2, 7, 0}, {9, 5, 0}, {0, 2, 1}, {8, 8, 0},
{4, 1, 0}, {9, 7, 0}, {1, 6, 1}, {9, 3, 0}, {9, 9, 0}, {3, 7, 0},
{5, 1, 0}, {0, 5, 0}, {8, 2, 0}, {0, 9, 0}, {7, 4, 1}, {9, 4, 0},
{4, 5, 0}, {9, 2, 0}
};
size_t i, count = sizeof(ent) / sizeof(*ent);
for (i = 0; i < count; i++) {
int ret = insert_test_item(btree, ent[i].key, ent[i].value);
ok1(ret == ent[i].expected_return);
}
}
static void test_find_traverse(struct btree *btree)
{
struct test_traverse_entry ent[] = {
{0, 9}, {1, 6}, {2, 7}, {3, 7}, {4, 5},
{5, 1}, {6, 4}, {7, 4}, {8, 2}, {9, 2}
};
size_t i, count = sizeof(ent) / sizeof(*ent);
btree_iterator iter;
i = 0;
for (btree_begin(btree, iter); btree_next(iter);) {
struct test_item *item = iter->item;
if (i >= count) {
fail("Too many items in btree according to forward traversal");
break;
}
ok1(lookup_test_item(btree, item->key) == item->value);
ok1(item->key == ent[i].key && item->value == ent[i].value);
i++;
}
if (i != count)
fail("Not enough items in btree according to forward traversal");
i = count;
for (btree_end(btree, iter); btree_prev(iter);) {
struct test_item *item = iter->item;
if (!i--) {
fail("Too many items in btree according to backward traversal");
break;
}
ok1(lookup_test_item(btree, item->key) == item->value);
ok1(item->key == ent[i].key && item->value == ent[i].value);
}
if (i != 0)
fail("Not enough items in btree according to backward traversal");
}
static btree_search_proto order_by_string;
static btree_search_implement(
order_by_string, //function name
const char*, //key type
int c = strcmp(a, b), //setup
c == 0, // a == b predicate
c < 0 // a < b predicate
)
//used in the test case to sort the test strings
static int compare_by_string(const void *ap, const void *bp)
{
const char * const *a = ap;
const char * const *b = bp;
return strcmp(*a, *b);
}
static void test_traverse(struct btree *btree, const char *sorted[], size_t count)
{
btree_iterator iter, iter2;
size_t i;
i = 0;
for (btree_begin(btree, iter); btree_next(iter);) {
if (i >= count) {
fail("Too many items in btree according to forward traversal");
break;
}
ok1(iter->item == sorted[i]);
btree_find_first(btree, sorted[i], iter2);
ok1(iter2->item == sorted[i]);
i++;
}
if (i != count)
fail("Not enough items in btree according to forward traversal");
i = count;
for (btree_end(btree, iter); btree_prev(iter);) {
if (!i--) {
fail("Too many items in btree according to backward traversal");
break;
}
ok1(iter->item == sorted[i]);
btree_find_first(btree, sorted[i], iter2);
ok1(iter2->item == sorted[i]);
}
if (i != 0)
fail("Not enough items in btree according to backward traversal");
}
#if 0
//(tries to) remove the key from both the btree and the test array. Returns 1 on success, 0 on failure.
//success is when an item is removed from the btree and the array, or when none is removed from either
//failure is when an item is removed from the btree but not the array or vice versa
//after removing, it tries removing again to make sure that removal returns 0
static int test_remove(struct btree *btree, struct btree_item items[], size_t *count, const char *key)
{
size_t i;
for (i = *count; i--;) {
if (!strcmp(items[i].key, key)) {
//item found in array
memmove(&items[i], &items[i+1], (*count-(i+1))*sizeof(*items));
(*count)--;
//puts("----------");
//btree_trace(btree);
//removal should succeed, as the key should be there
//this is not a contradiction; the test is performed twice
return btree_remove(btree, key) && !btree_remove(btree, key);
}
}
//removal should fail, as the key should not be there
//this is not redundant; the test is performed twice
return !btree_remove(btree, key) && !btree_remove(btree, key);
}
#endif
static void test_search_implement(void)
{
struct btree *btree = btree_new(order_by_string);
size_t i;
const char *unsorted[] = {
"md4",
"isaac",
"noerr",
"talloc_link",
"asearch",
"tap",
"crcsync",
"wwviaudio",
"array_size",
"alignof",
"str",
"read_write_all",
"grab_file",
"out",
"daemonize",
"array",
"crc",
"str_talloc",
"build_assert",
"talloc",
"alloc",
"endian",
"btree",
"typesafe_cb",
"check_type",
"list",
"ciniparser",
"ilog",
"ccan_tokenizer",
"tdb",
"block_pool",
"sparse_bsearch",
"container_of",
"stringmap",
"hash",
"short_types",
"ogg_to_pcm",
"antithread",
};
size_t count = sizeof(unsorted) / sizeof(*unsorted);
const char *sorted[count];
memcpy(sorted, unsorted, sizeof(sorted));
qsort(sorted, count, sizeof(*sorted), compare_by_string);
for (i=0; i<count; i++) {
btree_iterator iter;
if (btree_find_first(btree, unsorted[i], iter))
fail("btree_insert thinks the test array has duplicates, but it doesn't");
else
btree_insert_at(iter, unsorted[i]);
}
btree_trace(btree);
test_traverse(btree, sorted, count);
/*
//try removing items that should be in the tree
ok1(test_remove(btree, sorted, &count, "btree"));
ok1(test_remove(btree, sorted, &count, "ccan_tokenizer"));
ok1(test_remove(btree, sorted, &count, "endian"));
ok1(test_remove(btree, sorted, &count, "md4"));
ok1(test_remove(btree, sorted, &count, "asearch"));
ok1(test_remove(btree, sorted, &count, "alloc"));
ok1(test_remove(btree, sorted, &count, "build_assert"));
ok1(test_remove(btree, sorted, &count, "typesafe_cb"));
ok1(test_remove(btree, sorted, &count, "sparse_bsearch"));
ok1(test_remove(btree, sorted, &count, "stringmap"));
//try removing items that should not be in the tree
ok1(test_remove(btree, sorted, &count, "java"));
ok1(test_remove(btree, sorted, &count, "openoffice"));
ok1(test_remove(btree, sorted, &count, "firefox"));
ok1(test_remove(btree, sorted, &count, "linux"));
ok1(test_remove(btree, sorted, &count, ""));
//test the traversal again to make sure things are okay
test_traverse(btree, sorted, count);
//remove the rest of the items, then make sure tree is empty
while (count)
ok1(test_remove(btree, sorted, &count, sorted[count-1].key));
ok1(btree->root == NULL);
*/
btree_delete(btree);
}
int main(void)
{
struct btree *btree;
plan_tests(224);
btree = btree_new(order_by_key);
btree->destroy = destroy_test_item;
test_insert(btree);
test_find_traverse(btree);
btree_delete(btree);
test_search_implement();
return exit_status();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment