mirror of
https://salsa.debian.org/ha-team/libqb
synced 2026-01-21 12:36:54 +00:00
(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
963 lines
22 KiB
C
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;
|
|
}
|