mirror of
https://gitlab.gnome.org/GNOME/libxml2.git
synced 2024-12-24 21:33:51 +03:00
hash: Add hash table tests
Make sure to properly test removal from hash tables.
This commit is contained in:
parent
1425d8f67b
commit
4f221a7748
427
testdict.c
427
testdict.c
@ -3,6 +3,9 @@
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/dict.h>
|
||||
|
||||
|
||||
/**** dictionary tests ****/
|
||||
|
||||
/* #define WITH_PRINT */
|
||||
|
||||
static const char *seeds1[] = {
|
||||
@ -119,7 +122,8 @@ static void clean_strings(void) {
|
||||
/*
|
||||
* This tests the sub-dictionary support
|
||||
*/
|
||||
static int run_test2(xmlDictPtr parent) {
|
||||
static int
|
||||
test_subdict(xmlDictPtr parent) {
|
||||
int i, j;
|
||||
xmlDictPtr dict;
|
||||
int ret = 0;
|
||||
@ -283,19 +287,14 @@ static int run_test2(xmlDictPtr parent) {
|
||||
/*
|
||||
* Test a single dictionary
|
||||
*/
|
||||
static int run_test1(void) {
|
||||
static int
|
||||
test_dict(xmlDict *dict) {
|
||||
int i, j;
|
||||
xmlDictPtr dict;
|
||||
int ret = 0;
|
||||
xmlChar prefix[40];
|
||||
xmlChar *cur, *pref;
|
||||
const xmlChar *tmp;
|
||||
|
||||
dict = xmlDictCreate();
|
||||
if (dict == NULL) {
|
||||
fprintf(stderr, "Out of memory while creating dictionary\n");
|
||||
exit(1);
|
||||
}
|
||||
/* Cast to avoid buggy warning on MSVC. */
|
||||
memset((void *) test1, 0, sizeof(test1));
|
||||
|
||||
@ -391,17 +390,13 @@ static int run_test1(void) {
|
||||
}
|
||||
}
|
||||
|
||||
run_test2(dict);
|
||||
|
||||
xmlDictFree(dict);
|
||||
return(ret);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
LIBXML_TEST_VERSION
|
||||
static int
|
||||
testall_dict(void) {
|
||||
xmlDictPtr dict;
|
||||
int ret = 0;
|
||||
|
||||
strings1 = xmlMalloc(NB_STRINGS_MAX * sizeof(strings1[0]));
|
||||
memset(strings1, 0, NB_STRINGS_MAX * sizeof(strings1[0]));
|
||||
@ -417,17 +412,407 @@ int main(void)
|
||||
#ifdef WITH_PRINT
|
||||
print_strings();
|
||||
#endif
|
||||
ret = run_test1();
|
||||
if (ret == 0) {
|
||||
printf("dictionary tests succeeded %d strings\n", 2 * NB_STRINGS_MAX);
|
||||
} else {
|
||||
printf("dictionary tests failed with %d errors\n", nbErrors);
|
||||
|
||||
dict = xmlDictCreate();
|
||||
if (dict == NULL) {
|
||||
fprintf(stderr, "Out of memory while creating dictionary\n");
|
||||
exit(1);
|
||||
}
|
||||
if (test_dict(dict) != 0) {
|
||||
ret = 1;
|
||||
}
|
||||
if (test_subdict(dict) != 0) {
|
||||
ret = 1;
|
||||
}
|
||||
xmlDictFree(dict);
|
||||
|
||||
clean_strings();
|
||||
xmlFree(strings1);
|
||||
xmlFree(strings2);
|
||||
xmlFree(test1);
|
||||
xmlFree(test2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**** Hash table tests ****/
|
||||
|
||||
static unsigned
|
||||
rng_state[2] = { 123, 456 };
|
||||
|
||||
#define HASH_ROL(x,n) ((x) << (n) | ((x) & 0xFFFFFFFF) >> (32 - (n)))
|
||||
|
||||
#ifdef __clang__
|
||||
__attribute__ ((no_sanitize("unsigned-integer-overflow")))
|
||||
__attribute__ ((no_sanitize("unsigned-shift-base")))
|
||||
#endif
|
||||
static unsigned
|
||||
my_rand(unsigned max) {
|
||||
unsigned s0 = rng_state[0];
|
||||
unsigned s1 = rng_state[1];
|
||||
unsigned result = HASH_ROL(s0 * 0x9E3779BB, 5) * 5;
|
||||
|
||||
s1 ^= s0;
|
||||
rng_state[0] = HASH_ROL(s0, 26) ^ s1 ^ (s1 << 9);
|
||||
rng_state[1] = HASH_ROL(s1, 13);
|
||||
|
||||
return((result & 0xFFFFFFFF) % max);
|
||||
}
|
||||
|
||||
static xmlChar *
|
||||
gen_random_string(xmlChar id) {
|
||||
unsigned size = my_rand(64) + 1;
|
||||
unsigned id_pos = my_rand(size);
|
||||
size_t j;
|
||||
|
||||
xmlChar *str = xmlMalloc(size + 1);
|
||||
for (j = 0; j < size; j++) {
|
||||
str[j] = 'a' + my_rand(26);
|
||||
}
|
||||
str[id_pos] = id;
|
||||
str[size] = 0;
|
||||
|
||||
/* Generate QName in 75% of cases */
|
||||
if (size > 3 && my_rand(4) > 0) {
|
||||
unsigned colon_pos = my_rand(size - 3) + 1;
|
||||
|
||||
if (colon_pos >= id_pos)
|
||||
colon_pos++;
|
||||
str[colon_pos] = ':';
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
xmlChar **strings;
|
||||
size_t num_entries;
|
||||
size_t num_keys;
|
||||
size_t num_strings;
|
||||
size_t index;
|
||||
xmlChar id;
|
||||
} StringPool;
|
||||
|
||||
static StringPool *
|
||||
pool_new(size_t num_entries, size_t num_keys, xmlChar id) {
|
||||
StringPool *ret;
|
||||
size_t num_strings;
|
||||
|
||||
ret = xmlMalloc(sizeof(*ret));
|
||||
ret->num_entries = num_entries;
|
||||
ret->num_keys = num_keys;
|
||||
num_strings = num_entries * num_keys;
|
||||
ret->strings = xmlMalloc(num_strings * sizeof(ret->strings[0]));
|
||||
memset(ret->strings, 0, num_strings * sizeof(ret->strings[0]));
|
||||
ret->num_strings = num_strings;
|
||||
ret->index = 0;
|
||||
ret->id = id;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
pool_free(StringPool *pool) {
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < pool->num_strings; i++) {
|
||||
xmlFree(pool->strings[i]);
|
||||
}
|
||||
xmlFree(pool->strings);
|
||||
xmlFree(pool);
|
||||
}
|
||||
|
||||
static int
|
||||
pool_done(StringPool *pool) {
|
||||
return pool->index >= pool->num_strings;
|
||||
}
|
||||
|
||||
static void
|
||||
pool_reset(StringPool *pool) {
|
||||
pool->index = 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pool_bulk_insert(StringPool *pool, xmlHashTablePtr hash, size_t num) {
|
||||
size_t i, j;
|
||||
int ret = 0;
|
||||
|
||||
for (i = pool->index, j = 0; i < pool->num_strings && j < num; j++) {
|
||||
xmlChar *str[3];
|
||||
size_t k;
|
||||
|
||||
while (1) {
|
||||
xmlChar tmp_key[1];
|
||||
int res;
|
||||
|
||||
for (k = 0; k < pool->num_keys; k++)
|
||||
str[k] = gen_random_string(pool->id);
|
||||
|
||||
switch (pool->num_keys) {
|
||||
case 1:
|
||||
res = xmlHashAddEntry(hash, str[0], tmp_key);
|
||||
if (res == 0 &&
|
||||
xmlHashUpdateEntry(hash, str[0], str[0], NULL) != 0)
|
||||
ret = -1;
|
||||
break;
|
||||
case 2:
|
||||
res = xmlHashAddEntry2(hash, str[0], str[1], tmp_key);
|
||||
if (res == 0 &&
|
||||
xmlHashUpdateEntry2(hash, str[0], str[1], str[0],
|
||||
NULL) != 0)
|
||||
ret = -1;
|
||||
break;
|
||||
case 3:
|
||||
res = xmlHashAddEntry3(hash, str[0], str[1], str[2],
|
||||
tmp_key);
|
||||
if (res == 0 &&
|
||||
xmlHashUpdateEntry3(hash, str[0], str[1], str[2],
|
||||
str[0], NULL) != 0)
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (res == 0)
|
||||
break;
|
||||
for (k = 0; k < pool->num_keys; k++)
|
||||
xmlFree(str[k]);
|
||||
}
|
||||
|
||||
for (k = 0; k < pool->num_keys; k++)
|
||||
pool->strings[i++] = str[k];
|
||||
}
|
||||
|
||||
pool->index = i;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static xmlChar *
|
||||
hash_qlookup(xmlHashTable *hash, xmlChar **names, size_t num_keys) {
|
||||
xmlChar *prefix[3];
|
||||
const xmlChar *local[3];
|
||||
xmlChar *res;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < 3; ++i) {
|
||||
if (i >= num_keys) {
|
||||
prefix[i] = NULL;
|
||||
local[i] = NULL;
|
||||
} else {
|
||||
const xmlChar *name = names[i];
|
||||
const xmlChar *colon = BAD_CAST strchr((const char *) name, ':');
|
||||
|
||||
if (colon == NULL) {
|
||||
prefix[i] = NULL;
|
||||
local[i] = name;
|
||||
} else {
|
||||
prefix[i] = xmlStrndup(name, colon - name);
|
||||
local[i] = &colon[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res = xmlHashQLookup3(hash, prefix[0], local[0], prefix[1], local[1],
|
||||
prefix[2], local[2]);
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
xmlFree(prefix[i]);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int
|
||||
pool_bulk_lookup(StringPool *pool, xmlHashTablePtr hash, size_t num,
|
||||
int existing) {
|
||||
size_t i, j;
|
||||
int ret = 0;
|
||||
|
||||
for (i = pool->index, j = 0; i < pool->num_strings && j < num; j++) {
|
||||
xmlChar **str = &pool->strings[i];
|
||||
int q;
|
||||
|
||||
for (q = 0; q < 2; q++) {
|
||||
xmlChar *res = NULL;
|
||||
|
||||
if (q) {
|
||||
res = hash_qlookup(hash, str, pool->num_keys);
|
||||
} else {
|
||||
switch (pool->num_keys) {
|
||||
case 1:
|
||||
res = xmlHashLookup(hash, str[0]);
|
||||
break;
|
||||
case 2:
|
||||
res = xmlHashLookup2(hash, str[0], str[1]);
|
||||
break;
|
||||
case 3:
|
||||
res = xmlHashLookup3(hash, str[0], str[1], str[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
if (res != str[0])
|
||||
ret = -1;
|
||||
} else {
|
||||
if (res != NULL)
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
|
||||
i += pool->num_keys;
|
||||
}
|
||||
|
||||
pool->index = i;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
pool_bulk_remove(StringPool *pool, xmlHashTablePtr hash, size_t num) {
|
||||
size_t i, j;
|
||||
int ret = 0;
|
||||
|
||||
for (i = pool->index, j = 0; i < pool->num_strings && j < num; j++) {
|
||||
xmlChar **str = &pool->strings[i];
|
||||
int res = -1;
|
||||
|
||||
switch (pool->num_keys) {
|
||||
case 1:
|
||||
res = xmlHashRemoveEntry(hash, str[0], NULL);
|
||||
break;
|
||||
case 2:
|
||||
res = xmlHashRemoveEntry2(hash, str[0], str[1], NULL);
|
||||
break;
|
||||
case 3:
|
||||
res = xmlHashRemoveEntry3(hash, str[0], str[1], str[2], NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
if (res != 0)
|
||||
ret = -1;
|
||||
|
||||
i += pool->num_keys;
|
||||
}
|
||||
|
||||
pool->index = i;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
test_hash(size_t num_entries, size_t num_keys, int use_dict) {
|
||||
xmlDict *dict = NULL;
|
||||
xmlHashTable *hash;
|
||||
StringPool *pool1, *pool2;
|
||||
int ret = 0;
|
||||
|
||||
if (use_dict) {
|
||||
dict = xmlDictCreate();
|
||||
hash = xmlHashCreateDict(0, dict);
|
||||
} else {
|
||||
hash = xmlHashCreate(0);
|
||||
}
|
||||
pool1 = pool_new(num_entries, num_keys, '1');
|
||||
pool2 = pool_new(num_entries, num_keys, '2');
|
||||
|
||||
/* Insert all strings from pool2 and about half of pool1. */
|
||||
while (!pool_done(pool2)) {
|
||||
if (pool_bulk_insert(pool1, hash, my_rand(50)) != 0) {
|
||||
fprintf(stderr, "pool1: hash insert failed\n");
|
||||
ret = 1;
|
||||
}
|
||||
if (pool_bulk_insert(pool2, hash, my_rand(100)) != 0) {
|
||||
fprintf(stderr, "pool1: hash insert failed\n");
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check existing entries */
|
||||
pool_reset(pool2);
|
||||
if (pool_bulk_lookup(pool2, hash, pool2->num_entries, 1) != 0) {
|
||||
fprintf(stderr, "pool2: hash lookup failed\n");
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
/* Remove all strings from pool2 and insert the rest of pool1. */
|
||||
pool_reset(pool2);
|
||||
while (!pool_done(pool1) || !pool_done(pool2)) {
|
||||
if (pool_bulk_insert(pool1, hash, my_rand(50)) != 0) {
|
||||
fprintf(stderr, "pool1: hash insert failed\n");
|
||||
ret = 1;
|
||||
}
|
||||
if (pool_bulk_remove(pool2, hash, my_rand(100)) != 0) {
|
||||
fprintf(stderr, "pool2: hash remove failed\n");
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check existing entries */
|
||||
pool_reset(pool1);
|
||||
if (pool_bulk_lookup(pool1, hash, pool1->num_entries, 1) != 0) {
|
||||
fprintf(stderr, "pool1: hash lookup failed\n");
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
/* Check removed entries */
|
||||
pool_reset(pool2);
|
||||
if (pool_bulk_lookup(pool2, hash, pool2->num_entries, 0) != 0) {
|
||||
fprintf(stderr, "pool2: hash lookup succeeded unexpectedly\n");
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
pool_free(pool1);
|
||||
pool_free(pool2);
|
||||
xmlHashFree(hash, NULL);
|
||||
xmlDictFree(dict);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
testall_hash(void) {
|
||||
size_t num_keys;
|
||||
|
||||
for (num_keys = 1; num_keys <= 3; num_keys++) {
|
||||
size_t num_strings;
|
||||
size_t max_strings = num_keys == 1 ? 100000 : 1000;
|
||||
|
||||
for (num_strings = 10; num_strings <= max_strings; num_strings *= 10) {
|
||||
size_t reps, i;
|
||||
|
||||
reps = 1000 / num_strings;
|
||||
if (reps == 0)
|
||||
reps = 1;
|
||||
|
||||
for (i = 0; i < reps; i++) {
|
||||
if (test_hash(num_strings, num_keys, /* use_dict */ 0) != 0)
|
||||
return(1);
|
||||
}
|
||||
|
||||
if (test_hash(num_strings, num_keys, /* use_dict */ 1) != 0)
|
||||
return(1);
|
||||
}
|
||||
}
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
||||
/**** main ****/
|
||||
|
||||
int
|
||||
main(void) {
|
||||
int ret = 0;
|
||||
|
||||
LIBXML_TEST_VERSION
|
||||
|
||||
if (testall_dict() != 0) {
|
||||
fprintf(stderr, "dictionary tests failed\n");
|
||||
ret = 1;
|
||||
}
|
||||
if (testall_hash() != 0) {
|
||||
fprintf(stderr, "hash tests failed\n");
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
xmlCleanupParser();
|
||||
return(ret);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user