libqb/tests/check_map.c
Christine Caulfield 44a9379c89 skiplist: fix use-after-free in the skiplist traversal
(Patch from poki, only committed under my name because github is being
weird)

This used to happen when an iterator contained a reference on the item
to continue with, which got outdated when such item had been removed in
the interim, though it's original memory would still be -- mistakenly --
accessed. Actually such a condition is exercised with an existing
"test_map_iter_safety(ordered=true)" test, though it likely never run
under valgrind's supervision and standard memory checking harness was
too coarse (perhaps because of low memory pressure or other "lucky"
coincidence). Thankfully, the default, paranoid approach towards dynamic
memory handling in OpenBSD (free(3) call makes small chunks "junked",
i.e., filled with 0xdf bytes, see malloc.conf(5)) resulted in the
explicit segmentation fault when tripping over the happens-to-be-freed
pointer in the assumed iteration chain.

We solve the "out-of-sync iterator" issue with a twist, inverting
the responsibility to carry (and more widely, to contribute in the
propagation of) the up-to-date "forward" pointers, as clearly,
iterating over and over through the items would not be very scalable
(and it was not done, which had resulted in the first place).
So now, when any skiplist item is to be removed, its preceding item
gets the "forward" pointers recomputed as before, but then, they are
copied into "forward" pointers for the item to be removed, original
area containing them is disposed, and this preceding item just points
to the area primarily managed by the to-be-removed item (procedure
dubbed "takeover-and-repoint" in the comment). This itself gets
a special mark so that this area won't be dropped when that item gets
disposed, which rather happens with the disposal of the preceding item
that points to the "forward" memory area at hand and is not marked so.
This is believed to be sufficient to address out-of-band (iterator
based) access versus interim future iteration chain mangling, as these
operate de facto on the non-sparse, linear level of the skiplist.

Alternative approaches include:

    turning pointers-to-arrays into pointers-to-pointers-to-arrays to
    allow for explicit setting to NULL after free, and sharing this
    additional indirection -- this straightforward extension was
    attempted first, but shortly after, it became apparent it would
    be a nightmare with the current interprocedural dependencies

    extra tagging of the structures and adding complexities around
    checking the eligibility, like every other manipulation with the
    skiplist

    completely split life-cycle of "node" and "node->forward", i.e.,
    separate reference-counting etc.

Also said test was extended to push the corner case to the limit:
when to-resume-with item in the chain is being figured out, the
predecessors may be consulted (it is in that test), but the very
first predecessor is now removed as well, for good measure, as
it makes for boundary condition ^ 2.

Signed-off-by: Jan Pokorný jpokorny@redhat.com
2018-10-15 10:10:43 +01:00

963 lines
22 KiB
C

/*
* Copyright (c) 2011 Red Hat, Inc.
*
* All rights reserved.
*
* Author: Angus Salkeld <asalkeld@redhat.com>
*
* This file is part of libqb.
*
* libqb is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* libqb 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libqb. If not, see <http://www.gnu.org/licenses/>.
*/
#include "os_base.h"
#include "check_common.h"
#include <qb/qbdefs.h>
#include <qb/qblog.h>
#include <qb/qbmap.h>
const char *chars[] = {
"0","1","2","3","4","5","6","7","8","9",
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
NULL,
};
const char *chars2[] = {
"0","1","2","3","4","5","6","7","8","9",
"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
NULL,
};
static char *notified_key = NULL;
static void *notified_value = NULL;
static void *notified_new_value = NULL;
static void *notified_user_data = NULL;
static int32_t notified_event = 0;
static int32_t notified_event_prev = 0;
static int32_t notified_events = 0;
static void
my_map_notification_iter(uint32_t event,
char* key, void* old_value,
void* value, void* user_data)
{
const char *p;
void *data;
qb_map_t *m = (qb_map_t *)user_data;
qb_map_iter_t *it = qb_map_iter_create(m);
notified_events++;
for (p = qb_map_iter_next(it, &data); p; p = qb_map_iter_next(it, &data)) {
printf("%s > %s\n", p, (char*) data);
}
qb_map_iter_free(it);
}
/*
* create some entries
* add a notifier
* delete an entry
* in the notifier iterate over the map.
*/
static void
test_map_notifications_iter(qb_map_t *m)
{
int i;
qb_map_put(m, "k1", "one");
qb_map_put(m, "k12", "two");
qb_map_put(m, "k34", "three");
ck_assert_int_eq(qb_map_count_get(m), 3);
notified_events = 0;
i = qb_map_notify_add(m, NULL, my_map_notification_iter,
(QB_MAP_NOTIFY_DELETED |
QB_MAP_NOTIFY_RECURSIVE), m);
ck_assert_int_eq(i, 0);
qb_map_rm(m, "k12");
ck_assert_int_eq(notified_events, 1);
ck_assert_int_eq(qb_map_count_get(m), 2);
}
static void
test_map_simple(qb_map_t *m, const char *name)
{
int i;
const char *p;
void *data;
qb_map_iter_t *it;
qb_map_put(m, "k1", "one");
qb_map_put(m, "k12", "two");
qb_map_put(m, "k34", "three");
ck_assert_int_eq(qb_map_count_get(m), 3);
qb_map_put(m, "k3", "four");
ck_assert_int_eq(qb_map_count_get(m), 4);
it = qb_map_iter_create(m);
i = 0;
for (p = qb_map_iter_next(it, &data); p; p = qb_map_iter_next(it, &data)) {
printf("%25s(%d) %s > %s\n", name, i, p, (char*) data);
i++;
}
qb_map_iter_free(it);
ck_assert_int_eq(i, 4);
ck_assert_str_eq(qb_map_get(m, "k34"), "three");
ck_assert_str_eq(qb_map_get(m, "k1"), "one");
ck_assert_str_eq(qb_map_get(m, "k12"), "two");
ck_assert_str_eq(qb_map_get(m, "k3"), "four");
qb_map_rm(m, "k12");
ck_assert_int_eq(qb_map_count_get(m), 3);
qb_map_put(m, "9k", "nine");
qb_map_put(m, "k34", "not_three");
ck_assert_str_eq(qb_map_get(m, "k34"), "not_three");
ck_assert_int_eq(qb_map_count_get(m), 4);
qb_map_destroy(m);
}
static int32_t
my_traverse(const char *key, void *value, void *data)
{
ck_assert((*key) > 0);
return QB_FALSE;
}
static int32_t
check_order(const char *key, void *value, void *data)
{
int *o = (int*)data;
ck_assert_str_eq(chars[*o], key);
ck_assert_str_eq(chars[*o], value);
(*o)++;
return QB_FALSE;
}
static int32_t
check_order2(const char *key, void *value, void *data)
{
int *o = (int*)data;
ck_assert_str_eq(chars2[*o], key);
ck_assert_str_eq(chars2[*o], value);
(*o)++;
return QB_FALSE;
}
static void
test_map_search(qb_map_t* m)
{
int32_t i;
int32_t removed;
int order;
char c[2];
const char *p;
for (i = 0; chars[i]; i++) {
qb_map_put(m, chars[i], chars[i]);
}
qb_map_foreach(m, my_traverse, NULL);
ck_assert_int_eq(qb_map_count_get(m), (26*2 + 10));
order = 0;
qb_map_foreach(m, check_order, &order);
for (i = 0; i < 26; i++) {
removed = qb_map_rm(m, chars[i + 10]);
ck_assert(removed);
}
c[0] = '\0';
c[1] = '\0';
removed = qb_map_rm(m, c);
ck_assert(!removed);
qb_map_foreach(m, my_traverse, NULL);
ck_assert_int_eq(qb_map_count_get(m), 26+10);
order = 0;
qb_map_foreach(m, check_order2, &order);
for (i = 25; i >= 0; i--) {
qb_map_put(m, chars[i + 10], chars[i + 10]);
}
order = 0;
qb_map_foreach(m, check_order, &order);
c[0] = '0';
p = qb_map_get(m, c);
ck_assert(p && *p == *c);
c[0] = 'A';
p = qb_map_get(m, c);
ck_assert(p && *p == *c);
c[0] = 'a';
p = qb_map_get(m, c);
ck_assert(p && *p == *c);
c[0] = 'z';
p = qb_map_get(m, c);
ck_assert(p && *p == *c);
c[0] = '!';
p = qb_map_get(m, c);
ck_assert(p == NULL);
c[0] = '=';
p = qb_map_get(m, c);
ck_assert(p == NULL);
c[0] = '|';
p = qb_map_get(m, c);
ck_assert(p == NULL);
qb_map_destroy(m);
}
static void
my_map_notification(uint32_t event,
char* key, void* old_value,
void* value, void* user_data)
{
notified_key = key;
notified_value = old_value;
notified_new_value = value;
notified_user_data = user_data;
notified_event_prev = notified_event;
notified_event = event;
}
static void
my_map_notification_2(uint32_t event,
char* key, void* old_value,
void* value, void* user_data)
{
}
static void
test_map_remove(qb_map_t *m)
{
const char * a, *b, *c, *d;
int32_t i;
int32_t removed;
const char *remove_ch[] = {"o","m","k","j","i","g","f","e","d","b","a", NULL};
i = qb_map_notify_add(m, NULL, my_map_notification,
(QB_MAP_NOTIFY_DELETED|
QB_MAP_NOTIFY_REPLACED|
QB_MAP_NOTIFY_RECURSIVE),
m);
ck_assert_int_eq(i, 0);
for (i = 0; chars[i]; i++) {
qb_map_put(m, chars[i], chars[i]);
}
a = "0";
qb_map_put(m, a, a);
ck_assert(notified_key == chars[0]);
ck_assert(notified_value == chars[0]);
ck_assert(notified_user_data == m);
notified_key = NULL;
notified_value = NULL;
notified_user_data = NULL;
b = "5";
removed = qb_map_rm(m, b);
ck_assert(removed);
ck_assert(notified_key == chars[5]);
ck_assert(notified_value == chars[5]);
ck_assert(notified_user_data == m);
notified_key = NULL;
notified_value = NULL;
notified_user_data = NULL;
d = "1";
qb_map_put(m, d, d);
ck_assert(notified_key == chars[1]);
ck_assert(notified_value == chars[1]);
ck_assert(notified_user_data == m);
notified_key = NULL;
notified_value = NULL;
c = "2";
removed = qb_map_rm(m, c);
ck_assert(removed);
ck_assert(notified_key == chars[2]);
ck_assert(notified_value == chars[2]);
notified_key = NULL;
notified_value = NULL;
for (i = 0; remove_ch[i]; i++) {
removed = qb_map_rm(m, remove_ch[i]);
ck_assert(removed);
}
qb_map_destroy(m);
}
static void
test_map_notifications_basic(qb_map_t *m)
{
int32_t i;
/* with global notifier */
i = qb_map_notify_add(m, NULL, my_map_notification,
(QB_MAP_NOTIFY_INSERTED|
QB_MAP_NOTIFY_DELETED|
QB_MAP_NOTIFY_REPLACED|
QB_MAP_NOTIFY_RECURSIVE),
m);
ck_assert_int_eq(i, 0);
notified_key = NULL;
notified_value = NULL;
notified_new_value = NULL;
/* insert */
qb_map_put(m, "garden", "grow");
ck_assert_str_eq(notified_key, "garden");
ck_assert_str_eq(notified_new_value, "grow");
ck_assert(notified_user_data == m);
/* update */
qb_map_put(m, "garden", "green");
ck_assert_str_eq(notified_key, "garden");
ck_assert_str_eq(notified_value, "grow");
ck_assert_str_eq(notified_new_value, "green");
ck_assert(notified_user_data == m);
/* delete */
qb_map_rm(m, "garden");
ck_assert_str_eq(notified_key, "garden");
ck_assert_str_eq(notified_value, "green");
ck_assert(notified_user_data == m);
/* no event with notifier removed */
i = qb_map_notify_del(m, NULL, my_map_notification,
(QB_MAP_NOTIFY_INSERTED|
QB_MAP_NOTIFY_DELETED|
QB_MAP_NOTIFY_REPLACED|
QB_MAP_NOTIFY_RECURSIVE));
ck_assert_int_eq(i, 0);
notified_key = NULL;
notified_value = NULL;
notified_new_value = NULL;
qb_map_put(m, "age", "67");
ck_assert(notified_key == NULL);
ck_assert(notified_value == NULL);
ck_assert(notified_new_value == NULL);
/* deleting a non-existing notification */
i = qb_map_notify_del(m, "a", my_map_notification,
(QB_MAP_NOTIFY_INSERTED|
QB_MAP_NOTIFY_DELETED|
QB_MAP_NOTIFY_REPLACED|
QB_MAP_NOTIFY_RECURSIVE));
ck_assert_int_eq(i, -ENOENT);
/* test uniquess */
qb_map_put(m, "fred", "null");
i = qb_map_notify_add(m, "fred", my_map_notification,
QB_MAP_NOTIFY_REPLACED, m);
ck_assert_int_eq(i, 0);
i = qb_map_notify_add(m, "fred", my_map_notification,
QB_MAP_NOTIFY_REPLACED, m);
ck_assert_int_eq(i, -EEXIST);
}
/* test free'ing notifier
*
* input:
* only one can be added
* can only be added with NULL key (global)
* output:
* is the last notifier called (after deleted or replaced)
* recursive is implicit
*/
static void
test_map_notifications_free(qb_map_t *m)
{
int32_t i;
i = qb_map_notify_add(m, "not global", my_map_notification,
QB_MAP_NOTIFY_FREE, m);
ck_assert_int_eq(i, -EINVAL);
i = qb_map_notify_add(m, NULL, my_map_notification,
QB_MAP_NOTIFY_FREE, m);
ck_assert_int_eq(i, 0);
i = qb_map_notify_add(m, NULL, my_map_notification_2,
QB_MAP_NOTIFY_FREE, m);
ck_assert_int_eq(i, -EEXIST);
i = qb_map_notify_del_2(m, NULL, my_map_notification,
QB_MAP_NOTIFY_FREE, m);
ck_assert_int_eq(i, 0);
i = qb_map_notify_add(m, NULL, my_map_notification,
(QB_MAP_NOTIFY_FREE |
QB_MAP_NOTIFY_REPLACED |
QB_MAP_NOTIFY_DELETED |
QB_MAP_NOTIFY_RECURSIVE), m);
ck_assert_int_eq(i, 0);
qb_map_put(m, "garden", "grow");
/* update */
qb_map_put(m, "garden", "green");
ck_assert_int_eq(notified_event_prev, QB_MAP_NOTIFY_REPLACED);
ck_assert_int_eq(notified_event, QB_MAP_NOTIFY_FREE);
/* delete */
qb_map_rm(m, "garden");
ck_assert_int_eq(notified_event_prev, QB_MAP_NOTIFY_DELETED);
ck_assert_int_eq(notified_event, QB_MAP_NOTIFY_FREE);
}
static void
test_map_notifications_prefix(qb_map_t *m)
{
int32_t i;
/* with prefix notifier */
i = qb_map_notify_add(m, "add", my_map_notification,
(QB_MAP_NOTIFY_INSERTED|
QB_MAP_NOTIFY_DELETED|
QB_MAP_NOTIFY_REPLACED|
QB_MAP_NOTIFY_RECURSIVE),
&i);
ck_assert_int_eq(i, 0);
/* insert */
qb_map_put(m, "adder", "snake");
ck_assert_str_eq(notified_key, "adder");
ck_assert_str_eq(notified_new_value, "snake");
ck_assert(notified_user_data == &i);
/* insert (no match) */
notified_key = NULL;
notified_value = NULL;
notified_new_value = NULL;
qb_map_put(m, "adjust", "it");
ck_assert(notified_key == NULL);
ck_assert(notified_value == NULL);
ck_assert(notified_new_value == NULL);
/* update */
qb_map_put(m, "adder", "+++");
ck_assert_str_eq(notified_key, "adder");
ck_assert_str_eq(notified_value, "snake");
ck_assert_str_eq(notified_new_value, "+++");
/* delete */
qb_map_rm(m, "adder");
ck_assert_str_eq(notified_key, "adder");
ck_assert_str_eq(notified_value, "+++");
}
static void
test_map_traverse_ordered(qb_map_t *m)
{
int32_t i;
const char *p;
char *result;
void *data;
qb_map_iter_t *it = qb_map_iter_create(m);
for (i = 0; chars[i]; i++) {
qb_map_put(m, chars[i], chars[i]);
}
result = calloc(sizeof(char), 26 * 2 + 10 + 1);
i = 0;
for (p = qb_map_iter_next(it, &data); p; p = qb_map_iter_next(it, &data)) {
result[i] = *(char*) data;
i++;
}
qb_map_iter_free(it);
ck_assert_str_eq(result,
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
qb_map_destroy(m);
}
static int32_t
traverse_and_remove_func(const char *key, void *value, void *data)
{
int kk = random() % 30;
qb_map_t *m = (qb_map_t *)data;
qb_map_rm(m, chars[kk]);
qb_map_put(m, chars[kk+30], key);
return QB_FALSE;
}
static void
test_map_iter_safety(qb_map_t *m, int32_t ordered)
{
void *data;
void *data2;
const char *p;
const char *p2;
qb_map_iter_t *it;
qb_map_iter_t *it2;
int32_t found_good = QB_FALSE;
qb_map_put(m, "aaaa", "aye");
qb_map_put(m, "bbbb", "bee");
qb_map_put(m, "cccc", "sea");
it = qb_map_iter_create(m);
it2 = qb_map_iter_create(m);
while ((p = qb_map_iter_next(it, &data)) != NULL) {
printf("1: %s == %s\n", p, (char*)data);
if (strcmp(p, "bbbb") == 0) {
qb_map_rm(m, "bbbb");
qb_map_rm(m, "cccc");
qb_map_rm(m, "aaaa");
qb_map_put(m, "fffff", "yum");
while ((p2 = qb_map_iter_next(it2, &data2)) != NULL) {
printf("2: %s == %s\n", p2, (char*)data2);
if (strcmp(p2, "fffff") == 0) {
qb_map_put(m, "ggggg", "good");
}
}
qb_map_iter_free(it2);
}
if (strcmp(p, "ggggg") == 0) {
found_good = QB_TRUE;
}
}
qb_map_iter_free(it);
if (ordered) {
ck_assert_int_eq(found_good, QB_TRUE);
}
qb_map_destroy(m);
}
static void
test_map_iter_prefix(qb_map_t *m)
{
void *data;
const char *p;
qb_map_iter_t *it;
int count;
qb_map_put(m, "aaaa", "aye");
qb_map_put(m, "facc", "nope");
qb_map_put(m, "abbb", "bee");
qb_map_put(m, "a.ac", "nope");
qb_map_put(m, "aacc", "yip");
qb_map_put(m, "cacc", "nope");
qb_map_put(m, "c", "----");
count = 0;
it = qb_map_pref_iter_create(m, "aa");
while ((p = qb_map_iter_next(it, &data)) != NULL) {
printf("1: %s == %s\n", p, (char*)data);
count++;
}
qb_map_iter_free(it);
ck_assert_int_eq(count, 2);
count = 0;
it = qb_map_pref_iter_create(m, "a");
while ((p = qb_map_iter_next(it, &data)) != NULL) {
printf("2: %s == %s\n", p, (char*)data);
count++;
}
qb_map_iter_free(it);
ck_assert_int_eq(count, 4);
count = 0;
it = qb_map_pref_iter_create(m, "zz");
while ((p = qb_map_iter_next(it, &data)) != NULL) {
printf("??: %s == %s\n", p, (char*)data);
count++;
}
qb_map_iter_free(it);
ck_assert_int_eq(count, 0);
count = 0;
it = qb_map_pref_iter_create(m, "c");
while ((p = qb_map_iter_next(it, &data)) != NULL) {
printf("3: %s == %s\n", p, (char*)data);
count++;
}
qb_map_iter_free(it);
ck_assert_int_eq(count, 2);
qb_map_destroy(m);
}
static void
test_map_traverse_unordered(qb_map_t *m)
{
int32_t i;
srand(time(NULL));
for (i = 0; i < 30; i++) {
qb_map_put(m, chars[i], chars[i]);
}
qb_map_foreach(m, traverse_and_remove_func, m);
qb_map_destroy(m);
}
static int32_t
my_counter_traverse(const char *key, void *value, void *data)
{
int32_t *c = (int32_t*)data;
(*c)++;
return QB_FALSE;
}
static void
test_map_load(qb_map_t *m, const char* test_name)
{
char word[1000];
char *w;
FILE *fp;
int32_t res = 0;
int32_t count;
int32_t count2;
float ops;
float secs;
void *value;
qb_util_stopwatch_t *sw;
ck_assert(m != NULL);
sw = qb_util_stopwatch_create();
#define MAX_WORDS 100000
/*
* Load with dictionary
*/
fp = fopen("/usr/share/dict/words", "r");
qb_util_stopwatch_start(sw);
count = 0;
while (fgets(word, sizeof(word), fp) && count < MAX_WORDS) {
w = strdup(word);
qb_map_put(m, w, w);
count++;
}
qb_util_stopwatch_stop(sw);
ck_assert_int_eq(qb_map_count_get(m), count);
fclose(fp);
secs = qb_util_stopwatch_sec_elapsed_get(sw);
ops = (float)count / secs;
qb_log(LOG_INFO, "%25s %12.2f puts/sec (%d/%fs)\n", test_name, ops, count, secs);
/*
* Verify dictionary produces correct values
*/
fp = fopen("/usr/share/dict/words", "r");
qb_util_stopwatch_start(sw);
count2 = 0;
while (fgets(word, sizeof(word), fp) && count2 < MAX_WORDS) {
value = qb_map_get(m, word);
ck_assert_str_eq(word, value);
count2++;
}
qb_util_stopwatch_stop(sw);
fclose(fp);
secs = qb_util_stopwatch_sec_elapsed_get(sw);
ops = (float)count2 / secs;
qb_log(LOG_INFO, "%25s %12.2f gets/sec (%d/%fs)\n", test_name, ops, count2, secs);
/*
* time the iteration
*/
count2 = 0;
qb_util_stopwatch_start(sw);
qb_map_foreach(m, my_counter_traverse, &count2);
qb_util_stopwatch_stop(sw);
ck_assert_int_eq(qb_map_count_get(m), count2);
secs = qb_util_stopwatch_sec_elapsed_get(sw);
ops = (float)count2 / secs;
qb_log(LOG_INFO, "%25s %12.2f iters/sec (%d/%fs)\n", test_name, ops, count2, secs);
/*
* Delete all dictionary entries
*/
fp = fopen("/usr/share/dict/words", "r");
qb_util_stopwatch_start(sw);
count2 = 0;
while (fgets(word, sizeof(word), fp) && count2 < MAX_WORDS) {
res = qb_map_rm(m, word);
ck_assert_int_eq(res, QB_TRUE);
count2++;
}
qb_util_stopwatch_stop(sw);
ck_assert_int_eq(qb_map_count_get(m), 0);
fclose(fp);
secs = qb_util_stopwatch_sec_elapsed_get(sw);
ops = (float)count2 / secs;
qb_log(LOG_INFO, "%25s %12.2f dels/sec (%d/%fs)\n", test_name, ops, count2, secs);
}
START_TEST(test_skiplist_simple)
{
qb_map_t *m = qb_skiplist_create();
test_map_simple(m, __func__);
}
END_TEST
START_TEST(test_hashtable_simple)
{
qb_map_t *m = qb_hashtable_create(32);
test_map_simple(m, __func__);
}
END_TEST
START_TEST(test_trie_simple)
{
qb_map_t *m = qb_trie_create();
test_map_simple(m, __func__);
}
END_TEST
START_TEST(test_skiplist_search)
{
qb_map_t *m = qb_skiplist_create();
test_map_search(m);
}
END_TEST
START_TEST(test_trie_search)
{
qb_map_t *m = qb_trie_create();
test_map_search(m);
}
END_TEST
START_TEST(test_skiplist_remove)
{
qb_map_t *m = qb_skiplist_create();
test_map_remove(m);
}
END_TEST
START_TEST(test_hashtable_remove)
{
qb_map_t *m = qb_hashtable_create(256);
test_map_remove(m);
}
END_TEST
START_TEST(test_trie_notifications)
{
qb_map_t *m;
m = qb_trie_create();
test_map_remove(m);
m = qb_trie_create();
test_map_notifications_basic(m);
m = qb_trie_create();
test_map_notifications_prefix(m);
m = qb_trie_create();
test_map_notifications_free(m);
m = qb_trie_create();
test_map_notifications_iter(m);
}
END_TEST
START_TEST(test_hash_notifications)
{
qb_map_t *m;
m = qb_hashtable_create(256);
test_map_notifications_basic(m);
m = qb_hashtable_create(256);
test_map_notifications_free(m);
}
END_TEST
START_TEST(test_skiplist_notifications)
{
qb_map_t *m;
m = qb_skiplist_create();
test_map_notifications_basic(m);
m = qb_skiplist_create();
test_map_notifications_free(m);
}
END_TEST
START_TEST(test_skiplist_traverse)
{
qb_map_t *m;
m = qb_skiplist_create();
test_map_traverse_ordered(m);
m = qb_skiplist_create();
test_map_traverse_unordered(m);
m = qb_skiplist_create();
test_map_iter_safety(m, QB_TRUE);
}
END_TEST
START_TEST(test_hashtable_traverse)
{
qb_map_t *m;
m = qb_hashtable_create(256);
test_map_traverse_unordered(m);
m = qb_hashtable_create(256);
test_map_iter_safety(m, QB_FALSE);
}
END_TEST
START_TEST(test_trie_traverse)
{
qb_map_t *m;
m = qb_trie_create();
test_map_traverse_unordered(m);
m = qb_trie_create();
test_map_iter_safety(m, QB_FALSE);
m = qb_trie_create();
test_map_iter_prefix(m);
}
END_TEST
START_TEST(test_skiplist_load)
{
qb_map_t *m;
if (access("/usr/share/dict/words", R_OK) != 0) {
printf("no dict/words - not testing\n");
return;
}
m = qb_skiplist_create();
test_map_load(m, __func__);
}
END_TEST
START_TEST(test_hashtable_load)
{
qb_map_t *m;
if (access("/usr/share/dict/words", R_OK) != 0) {
printf("no dict/words - not testing\n");
return;
}
m = qb_hashtable_create(100000);
test_map_load(m, __func__);
}
END_TEST
START_TEST(test_trie_load)
{
qb_map_t *m;
if (access("/usr/share/dict/words", R_OK) != 0) {
printf("no dict/words - not testing\n");
return;
}
m = qb_trie_create();
test_map_load(m, __func__);
}
END_TEST
/*
* From Honza: https://github.com/asalkeld/libqb/issues/44
*/
START_TEST(test_trie_partial_iterate)
{
qb_map_t *map;
qb_map_iter_t *iter;
const char *res;
char *item;
int rc;
ck_assert((map = qb_trie_create()) != NULL);
qb_map_put(map, strdup("testobj.testkey"), strdup("one"));
qb_map_put(map, strdup("testobj.testkey2"), strdup("two"));
iter = qb_map_pref_iter_create(map, "testobj.");
ck_assert(iter != NULL);
res = qb_map_iter_next(iter, (void **)&item);
fprintf(stderr, "%s = %s\n", res, item);
qb_map_iter_free(iter);
item = qb_map_get(map, "testobj.testkey");
ck_assert_str_eq(item, "one");
rc = qb_map_rm(map, "testobj.testkey");
ck_assert(rc == QB_TRUE);
item = qb_map_get(map, "testobj.testkey");
ck_assert(item == NULL);
}
END_TEST
static Suite *
map_suite(void)
{
TCase *tc;
Suite *s = suite_create("qb_map");
add_tcase(s, tc, test_skiplist_simple);
add_tcase(s, tc, test_hashtable_simple);
add_tcase(s, tc, test_trie_simple);
add_tcase(s, tc, test_trie_partial_iterate);
add_tcase(s, tc, test_skiplist_remove);
add_tcase(s, tc, test_hashtable_remove);
add_tcase(s, tc, test_trie_notifications);
add_tcase(s, tc, test_hash_notifications);
add_tcase(s, tc, test_skiplist_notifications);
add_tcase(s, tc, test_skiplist_search);
/*
* No hashtable_search as it assumes an ordered
* collection
*/
add_tcase(s, tc, test_trie_search);
add_tcase(s, tc, test_skiplist_traverse);
add_tcase(s, tc, test_hashtable_traverse);
add_tcase(s, tc, test_trie_traverse);
add_tcase(s, tc, test_skiplist_load, 30);
add_tcase(s, tc, test_hashtable_load, 30);
add_tcase(s, tc, test_trie_load, 30);
return s;
}
int32_t
main(void)
{
int32_t number_failed;
Suite *s = map_suite();
SRunner *sr = srunner_create(s);
qb_log_init("check", LOG_USER, LOG_EMERG);
atexit(qb_log_fini);
qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);
qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD,
QB_LOG_FILTER_FILE, "*", LOG_INFO);
qb_log_format_set(QB_LOG_STDERR, "%f:%l %p %b");
qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
srunner_run_all(sr, CK_VERBOSE);
number_failed = srunner_ntests_failed(sr);
srunner_free(sr);
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}