Viktor Söderqvist dcac3e1499
Fix undefined-santitizer warning in rax test ()
Fix the warning introduced in :

```
unit/test_rax.c:168:15: runtime error: left shift of 36625 by 16 places cannot be represented in type 'int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior unit/test_rax.c:168:15 in 
Fuzz test in mode 1 [7504]: 
```

Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2024-10-03 17:34:03 +02:00

1026 lines
35 KiB
C

/* Rax -- A radix tree implementation.
*
* Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <errno.h>
#include "../rax.c"
#include "../mt19937-64.c"
#include "test_help.h"
uint16_t crc16(const char *buf, int len); /* From crc16.c */
long long _ustime(void); /* From test_crc64combine.c */
/* ---------------------------------------------------------------------------
* Simple hash table implementation, no rehashing, just chaining. This is
* used in order to test the radix tree implementation against something that
* will always "tell the truth" :-) */
/* This is huge but we want it fast enough without reahshing needed. */
#define HT_TABLE_SIZE 100000
typedef struct htNode {
uint64_t keylen;
unsigned char *key;
void *data;
struct htNode *next;
} htNode;
typedef struct ht {
uint64_t numele;
htNode *table[HT_TABLE_SIZE];
} hashtable;
/* Create a new hash table. */
hashtable *htNew(void) {
hashtable *ht = zcalloc(sizeof(*ht));
ht->numele = 0;
return ht;
}
/* djb2 hash function. */
uint32_t htHash(unsigned char *s, size_t len) {
uint32_t hash = 5381;
for (size_t i = 0; i < len; i++) hash = hash * 33 + s[i];
return hash % HT_TABLE_SIZE;
}
/* Low level hash table lookup function. */
htNode *htRawLookup(hashtable *t, unsigned char *s, size_t len, uint32_t *hash, htNode ***parentlink) {
uint32_t h = htHash(s, len);
if (hash) *hash = h;
htNode *n = t->table[h];
if (parentlink) *parentlink = &t->table[h];
while (n) {
if (n->keylen == len && memcmp(n->key, s, len) == 0) return n;
if (parentlink) *parentlink = &n->next;
n = n->next;
}
return NULL;
}
/* Add an element to the hash table, return 1 if the element is new,
* 0 if it existed and the value was updated to the new one. */
int htAdd(hashtable *t, unsigned char *s, size_t len, void *data) {
uint32_t hash;
htNode *n = htRawLookup(t, s, len, &hash, NULL);
if (!n) {
n = zmalloc(sizeof(*n));
n->key = zmalloc(len);
memcpy(n->key, s, len);
n->keylen = len;
n->data = data;
n->next = t->table[hash];
t->table[hash] = n;
t->numele++;
return 1;
} else {
n->data = data;
return 0;
}
}
/* Remove the specified element, returns 1 on success, 0 if the element
* was not there already. */
int htRem(hashtable *t, unsigned char *s, size_t len) {
htNode **parentlink;
htNode *n = htRawLookup(t, s, len, NULL, &parentlink);
if (!n) return 0;
*parentlink = n->next;
zfree(n->key);
zfree(n);
t->numele--;
return 1;
}
void *htNotFound = (void *)"ht-not-found";
/* Find an element inside the hash table. Returns htNotFound if the
* element is not there, otherwise returns the associated value. */
void *htFind(hashtable *t, unsigned char *s, size_t len) {
htNode *n = htRawLookup(t, s, len, NULL, NULL);
if (!n) return htNotFound;
return n->data;
}
/* Free the whole hash table including all the linked nodes. */
void htFree(hashtable *ht) {
for (int j = 0; j < HT_TABLE_SIZE; j++) {
htNode *next = ht->table[j];
while (next) {
htNode *this = next;
next = this->next;
zfree(this->key);
zfree(this);
}
}
zfree(ht);
}
/* --------------------------------------------------------------------------
* Utility functions to generate keys, check time usage and so forth.
* -------------------------------------------------------------------------*/
/* This is a simple Feistel network in order to turn every possible
* uint32_t input into another "randomly" looking uint32_t. It is a
* one to one map so there are no repetitions. */
static uint32_t int2int(uint32_t input) {
uint16_t l = input & 0xffff;
uint16_t r = input >> 16;
for (int i = 0; i < 8; i++) {
uint16_t nl = r;
uint16_t F = (((r * 31) + (r >> 5) + 7 * 371) ^ r) & 0xffff;
r = l ^ F;
l = nl;
}
return ((uint32_t)r << 16) | l;
}
/* Turn an uint32_t integer into an alphanumerical key and return its
* length. This function is used in order to generate keys that have
* a large charset, so that the radix tree can be testsed with many
* children per node. */
static size_t int2alphakey(char *s, size_t maxlen, uint32_t i) {
const char *set = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789";
const size_t setlen = 62;
if (maxlen == 0) return 0;
maxlen--; /* Space for null term char. */
size_t len = 0;
while (len < maxlen) {
s[len++] = set[i % setlen];
i /= setlen;
if (i == 0) break;
}
s[len] = '\0';
return len;
}
/* Turn the integer 'i' into a key according to 'mode'.
* KEY_INT: Just represents the integer as a string.
* KEY_UNIQUE_ALPHA: Turn it into a random-looking alphanumerical string
* according to the int2alphakey() function, so that
* at every integer is mapped a different string.
* KEY_RANDOM: Totally random string up to maxlen bytes.
* KEY_RANDOM_ALPHA: Alphanumerical random string up to maxlen bytes.
* KEY_RANDOM_SMALL_CSET: Small charset random strings.
* KEY_CHAIN: 'i' times the character "A". */
#define KEY_INT 0
#define KEY_UNIQUE_ALPHA 1
#define KEY_RANDOM 2
#define KEY_RANDOM_ALPHA 3
#define KEY_RANDOM_SMALL_CSET 4
#define KEY_CHAIN 5
static size_t int2key(char *s, size_t maxlen, uint32_t i, int mode) {
if (mode == KEY_INT) {
return snprintf(s, maxlen, "%lu", (unsigned long)i);
} else if (mode == KEY_UNIQUE_ALPHA) {
if (maxlen > 16) maxlen = 16;
i = int2int(i);
return int2alphakey(s, maxlen, i);
} else if (mode == KEY_RANDOM) {
if (maxlen > 16) maxlen = 16;
int r = genrand64_int64() % maxlen;
for (int i = 0; i < r; i++) s[i] = genrand64_int64() & 0xff;
return r;
} else if (mode == KEY_RANDOM_ALPHA) {
if (maxlen > 16) maxlen = 16;
int r = genrand64_int64() % maxlen;
for (int i = 0; i < r; i++) s[i] = 'A' + genrand64_int64() % ('z' - 'A' + 1);
return r;
} else if (mode == KEY_RANDOM_SMALL_CSET) {
if (maxlen > 16) maxlen = 16;
int r = genrand64_int64() % maxlen;
for (int i = 0; i < r; i++) s[i] = 'A' + genrand64_int64() % 4;
return r;
} else if (mode == KEY_CHAIN) {
if (i > maxlen) i = maxlen;
memset(s, 'A', i);
return i;
} else {
return 0;
}
}
/* -------------------------------------------------------------------------- */
/* Perform a fuzz test, returns 0 on success, 1 on error. */
int fuzzTest(int keymode, size_t count, double addprob, double remprob) {
hashtable *ht = htNew();
rax *rax = raxNew();
printf("Fuzz test in mode %d [%zu]: ", keymode, count);
fflush(stdout);
/* Perform random operations on both the dictionaries. */
for (size_t i = 0; i < count; i++) {
unsigned char key[1024];
uint32_t keylen;
/* Insert element. */
if ((double)genrand64_int64() / RAND_MAX < addprob) {
keylen = int2key((char *)key, sizeof(key), i, keymode);
void *val = (void *)(unsigned long)genrand64_int64();
/* Stress NULL values more often, they use a special encoding. */
if (!(genrand64_int64() % 100)) val = NULL;
int retval1 = htAdd(ht, key, keylen, val);
int retval2 = raxInsert(rax, key, keylen, val, NULL);
if (retval1 != retval2) {
printf("Fuzz: key insertion reported mismatching value in HT/RAX\n");
return 1;
}
}
/* Remove element. */
if ((double)genrand64_int64() / RAND_MAX < remprob) {
keylen = int2key((char *)key, sizeof(key), i, keymode);
int retval1 = htRem(ht, key, keylen);
int retval2 = raxRemove(rax, key, keylen, NULL);
if (retval1 != retval2) {
printf("Fuzz: key deletion of '%.*s' reported mismatching "
"value in HT=%d RAX=%d\n",
(int)keylen, (char *)key, retval1, retval2);
return 1;
}
}
}
/* Check that count matches. */
if (ht->numele != raxSize(rax)) {
printf("Fuzz: HT / RAX keys count mismatch: %lu vs %lu\n", (unsigned long)ht->numele,
(unsigned long)raxSize(rax));
return 1;
}
printf("%lu elements inserted\n", (unsigned long)ht->numele);
/* Check that elements match. */
raxIterator iter;
raxStart(&iter, rax);
raxSeek(&iter, "^", NULL, 0);
size_t numkeys = 0;
while (raxNext(&iter)) {
void *val1 = htFind(ht, iter.key, iter.key_len);
void *val2 = NULL;
raxFind(rax, iter.key, iter.key_len, &val2);
if (val1 != val2) {
printf("Fuzz: HT=%p, RAX=%p value do not match "
"for key %.*s\n",
val1, val2, (int)iter.key_len, (char *)iter.key);
return 1;
}
numkeys++;
}
/* Check that the iterator reported all the elements. */
if (ht->numele != numkeys) {
printf("Fuzz: the iterator reported %lu keys instead of %lu\n", (unsigned long)numkeys,
(unsigned long)ht->numele);
return 1;
}
raxStop(&iter);
raxFree(rax);
htFree(ht);
return 0;
}
/* Redis Cluster alike fuzz testing.
*
* This test simulates the radix tree usage made by Redis Cluster in order
* to maintain the hash slot -> keys mappig. The keys are alphanumerical
* but the first two bytes that are binary (and are the key hashed).
*
* In this test there is no comparison with the hash table, the only goal
* is to crash the radix tree implementation, or to trigger Valgrind
* warnings. */
int fuzzTestCluster(size_t count, double addprob, double remprob) {
unsigned char key[128];
int keylen = 0;
printf("Cluster Fuzz test [keys:%zu keylen:%d]: ", count, keylen);
fflush(stdout);
rax *rax = raxNew();
/* This is our template to generate keys. The first two bytes will
* be replaced with the binary redis cluster hash slot. */
keylen = snprintf((char *)key, sizeof(key), "__geocode:2e68e5df3624");
char *cset = "0123456789abcdef";
for (unsigned long j = 0; j < count; j++) {
/* Generate a random key by altering our template key. */
/* With a given probability, let's use a common prefix so that there
* is a subset of keys that have an higher percentage of probability
* of being hit again and again. */
size_t commonprefix = genrand64_int64() & 0xf;
if (commonprefix == 0) memcpy(key + 10, "2e68e5", 6);
/* Alter a random char in the key. */
int pos = 10 + genrand64_int64() % 12;
key[pos] = cset[genrand64_int64() % 16];
/* Compute the Redis Cluster hash slot to set the first two
* binary bytes of the key. */
int hashslot = crc16((char *)key, keylen) & 0x3FFF;
key[0] = (hashslot >> 8) & 0xff;
key[1] = hashslot & 0xff;
/* Insert element. */
if ((double)genrand64_int64() / RAND_MAX < addprob) {
raxInsert(rax, key, keylen, NULL, NULL);
TEST_ASSERT(raxAllocSize(rax) == zmalloc_used_memory());
}
/* Remove element. */
if ((double)genrand64_int64() / RAND_MAX < remprob) {
raxRemove(rax, key, keylen, NULL);
TEST_ASSERT(raxAllocSize(rax) == zmalloc_used_memory());
}
}
size_t finalkeys = raxSize(rax);
raxFree(rax);
printf("ok with %zu final keys\n", finalkeys);
return 0;
}
/* Iterator fuzz testing. Compared the items returned by the Rax iterator with
* a C implementation obtained by sorting the inserted strings in a linear
* array. */
typedef struct arrayItem {
unsigned char *key;
size_t key_len;
} arrayItem;
/* Utility functions used with qsort() in order to sort the array of strings
* in the same way Rax sorts keys (which is, lexicographically considering
* every byte an unsigned integer. */
int compareAB(const unsigned char *keya, size_t lena, const unsigned char *keyb, size_t lenb) {
size_t minlen = (lena <= lenb) ? lena : lenb;
int retval = memcmp(keya, keyb, minlen);
if (lena == lenb || retval != 0) return retval;
return (lena > lenb) ? 1 : -1;
}
int compareArrayItems(const void *aptr, const void *bptr) {
const arrayItem *a = aptr;
const arrayItem *b = bptr;
return compareAB(a->key, a->key_len, b->key, b->key_len);
}
/* Seek an element in the array, returning the seek index (the index inside the
* array). If the seek is not possible (== operator and key not found or empty
* array) -1 is returned. */
int arraySeek(arrayItem *array, int count, unsigned char *key, size_t len, char *op) {
if (count == 0) return -1;
if (op[0] == '^') return 0;
if (op[0] == '$') return count - 1;
int eq = 0, lt = 0, gt = 0;
if (op[1] == '=') eq = 1;
if (op[0] == '<') lt = 1;
if (op[0] == '>') gt = 1;
int i;
for (i = 0; i < count; i++) {
int cmp = compareAB(array[i].key, array[i].key_len, key, len);
if (eq && !cmp) return i;
if (cmp > 0 && gt) return i;
if (cmp >= 0 && lt) {
i--;
break;
}
}
if (lt && i == count) return count - 1;
if (i < 0 || i >= count) return -1;
return i;
}
int iteratorFuzzTest(int keymode, size_t count) {
count = genrand64_int64() % count;
rax *rax = raxNew();
arrayItem *array = zmalloc(sizeof(arrayItem) * count);
/* Fill a radix tree and a linear array with some data. */
unsigned char key[1024];
size_t j = 0;
for (size_t i = 0; i < count; i++) {
uint32_t keylen = int2key((char *)key, sizeof(key), i, keymode);
void *val = (void *)(unsigned long)htHash(key, keylen);
if (raxInsert(rax, key, keylen, val, NULL)) {
array[j].key = zmalloc(keylen);
array[j].key_len = keylen;
memcpy(array[j].key, key, keylen);
j++;
}
}
count = raxSize(rax);
/* Sort the array. */
qsort(array, count, sizeof(arrayItem), compareArrayItems);
/* Perform a random seek operation. */
uint32_t keylen = int2key((char *)key, sizeof(key), genrand64_int64() % (count ? count : 1), keymode);
raxIterator iter;
raxStart(&iter, rax);
char *seekops[] = {"==", ">=", "<=", ">", "<", "^", "$"};
char *seekop = seekops[genrand64_int64() % 7];
raxSeek(&iter, seekop, key, keylen);
int seekidx = arraySeek(array, count, key, keylen, seekop);
int next = genrand64_int64() % 2;
int iteration = 0;
while (1) {
int rax_res;
int array_res;
unsigned char *array_key = NULL;
size_t array_key_len = 0;
array_res = (seekidx == -1) ? 0 : 1;
if (array_res) {
if (next && seekidx == (signed)count) array_res = 0;
if (!next && seekidx == -1) array_res = 0;
if (array_res != 0) {
array_key = array[seekidx].key;
array_key_len = array[seekidx].key_len;
}
}
if (next) {
rax_res = raxNext(&iter);
if (array_res) seekidx++;
} else {
rax_res = raxPrev(&iter);
if (array_res) seekidx--;
}
/* Both the iteratos should agree about EOF. */
if (array_res != rax_res) {
printf("Iter fuzz: iterators do not agree about EOF "
"at iteration %d: "
"array_more=%d rax_more=%d next=%d\n",
iteration, array_res, rax_res, next);
return 1;
}
if (array_res == 0) break; /* End of iteration reached. */
/* Check that the returned keys are the same. */
if (iter.key_len != array_key_len || memcmp(iter.key, array_key, iter.key_len)) {
printf("Iter fuzz: returned element %d mismatch\n", iteration);
printf("SEEKOP was %s\n", seekop);
if (keymode != KEY_RANDOM) {
printf("\n");
printf("BUG SEEKING: %s %.*s\n", seekop, keylen, key);
printf("%.*s (iter) VS %.*s (array) next=%d idx=%d "
"count=%lu keymode=%d\n",
(int)iter.key_len, (char *)iter.key, (int)array_key_len, (char *)array_key, next, seekidx,
(unsigned long)count, keymode);
if (count < 500) {
printf("\n");
for (unsigned int j = 0; j < count; j++) {
printf("%d) '%.*s'\n", j, (int)array[j].key_len, array[j].key);
}
}
exit(1);
}
return 1;
}
iteration++;
}
for (unsigned int i = 0; i < count; i++) zfree(array[i].key);
zfree(array);
raxStop(&iter);
raxFree(rax);
return 0;
}
/* Test the random walk function. */
int test_raxRandomWalk(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
rax *t = raxNew();
char *toadd[] = {"alligator", "alien", "byword", "chromodynamic", "romane", "romanus", "romulus", "rubens",
"ruber", "rubicon", "rubicundus", "all", "rub", "by", NULL};
long numele;
for (numele = 0; toadd[numele] != NULL; numele++) {
raxInsert(t, (unsigned char *)toadd[numele], strlen(toadd[numele]), (void *)numele, NULL);
TEST_ASSERT(raxAllocSize(t) == zmalloc_used_memory());
}
raxIterator iter;
raxStart(&iter, t);
raxSeek(&iter, "^", NULL, 0);
int maxloops = 100000;
while (raxRandomWalk(&iter, 0) && maxloops--) {
int nulls = 0;
for (long i = 0; i < numele; i++) {
if (toadd[i] == NULL) {
nulls++;
continue;
}
if (strlen(toadd[i]) == iter.key_len && memcmp(toadd[i], iter.key, iter.key_len) == 0) {
toadd[i] = NULL;
nulls++;
}
}
if (nulls == numele) break;
}
if (maxloops == 0) {
printf("randomWalkTest() is unable to report all the elements "
"after 100k iterations!\n");
return 1;
}
raxStop(&iter);
raxFree(t);
return 0;
}
int test_raxIteratorUnitTests(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
rax *t = raxNew();
char *toadd[] = {"alligator", "alien", "byword", "chromodynamic", "romane", "romanus", "romulus", "rubens",
"ruber", "rubicon", "rubicundus", "all", "rub", "by", NULL};
for (int x = 0; x < 10000; x++) genrand64_int64();
long items = 0;
while (toadd[items] != NULL) items++;
for (long i = 0; i < items; i++) {
raxInsert(t, (unsigned char *)toadd[i], strlen(toadd[i]), (void *)i, NULL);
TEST_ASSERT(raxAllocSize(t) == zmalloc_used_memory());
}
raxIterator iter;
raxStart(&iter, t);
struct {
char *seek;
size_t seeklen;
char *seekop;
char *expected;
} tests[] = {/* Seek value. */ /* Expected result. */
{"rpxxx", 5, "<=", "romulus"},
{"rom", 3, ">=", "romane"},
{"rub", 3, ">=", "rub"},
{"rub", 3, ">", "rubens"},
{"rub", 3, "<", "romulus"},
{"rom", 3, ">", "romane"},
{"chro", 4, ">", "chromodynamic"},
{"chro", 4, "<", "byword"},
{"chromz", 6, "<", "chromodynamic"},
{"", 0, "^", "alien"},
{"zorro", 5, "<=", "rubicundus"},
{"zorro", 5, "<", "rubicundus"},
{"zorro", 5, "<", "rubicundus"},
{"", 0, "$", "rubicundus"},
{"ro", 2, ">=", "romane"},
{"zo", 2, ">", NULL},
{"zo", 2, "==", NULL},
{"romane", 6, "==", "romane"}};
for (int i = 0; tests[i].expected != NULL; i++) {
raxSeek(&iter, tests[i].seekop, (unsigned char *)tests[i].seek, tests[i].seeklen);
int retval = raxNext(&iter);
if (tests[i].expected != NULL) {
if (strlen(tests[i].expected) != iter.key_len || memcmp(tests[i].expected, iter.key, iter.key_len) != 0) {
printf("Iterator unit test error: "
"test %d, %s expected, %.*s reported\n",
i, tests[i].expected, (int)iter.key_len, (char *)iter.key);
return 1;
}
} else {
if (retval != 0) {
printf("Iterator unit test error: "
"EOF expected in test %d\n",
i);
return 1;
}
}
}
raxStop(&iter);
raxFree(t);
return 0;
}
/* Test that raxInsert() / raxTryInsert() overwrite semantic
* works as expected. */
int test_raxTryInsertUnitTests(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
rax *t = raxNew();
raxInsert(t, (unsigned char *)"FOO", 3, (void *)(long)1, NULL);
void *old, *val;
raxTryInsert(t, (unsigned char *)"FOO", 3, (void *)(long)2, &old);
if (old != (void *)(long)1) {
printf("Old value not returned correctly by raxTryInsert(): %p", old);
return 1;
}
val = NULL;
raxFind(t, (unsigned char *)"FOO", 3, &val);
if (val != (void *)(long)1) {
printf("FOO value mismatch: is %p instead of 1", val);
return 1;
}
raxInsert(t, (unsigned char *)"FOO", 3, (void *)(long)2, NULL);
val = NULL;
raxFind(t, (unsigned char *)"FOO", 3, &val);
if (val != (void *)(long)2) {
printf("FOO value mismatch: is %p instead of 2", val);
return 1;
}
raxFree(t);
return 0;
}
/* Regression test #1: Iterator wrong element returned after seek. */
int test_raxRegressionTest1(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
rax *rax = raxNew();
raxInsert(rax, (unsigned char *)"LKE", 3, (void *)(long)1, NULL);
raxInsert(rax, (unsigned char *)"TQ", 2, (void *)(long)2, NULL);
raxInsert(rax, (unsigned char *)"B", 1, (void *)(long)3, NULL);
raxInsert(rax, (unsigned char *)"FY", 2, (void *)(long)4, NULL);
raxInsert(rax, (unsigned char *)"WI", 2, (void *)(long)5, NULL);
raxIterator iter;
raxStart(&iter, rax);
raxSeek(&iter, ">", (unsigned char *)"FMP", 3);
if (raxNext(&iter)) {
if (iter.key_len != 2 || memcmp(iter.key, "FY", 2)) {
printf("Regression test 1 failed: 'FY' expected, got: '%.*s'\n", (int)iter.key_len, (char *)iter.key);
return 1;
}
}
raxStop(&iter);
raxFree(rax);
return 0;
}
/* Regression test #2: Crash when mixing NULL and not NULL values. */
int test_raxRegressionTest2(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
rax *rt = raxNew();
raxInsert(rt, (unsigned char *)"a", 1, (void *)100, NULL);
raxInsert(rt, (unsigned char *)"ab", 2, (void *)101, NULL);
raxInsert(rt, (unsigned char *)"abc", 3, (void *)NULL, NULL);
raxInsert(rt, (unsigned char *)"abcd", 4, (void *)NULL, NULL);
raxInsert(rt, (unsigned char *)"abc", 3, (void *)102, NULL);
raxFree(rt);
return 0;
}
/* Regression test #3: Wrong access at node value in raxRemoveChild()
* when iskey == 1 and isnull == 1: the memmove() was performed including
* the value length regardless of the fact there was no actual value.
*
* Note that this test always returns success but will trigger a
* Valgrind error. */
int test_raxRegressionTest3(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
rax *rt = raxNew();
raxInsert(rt, (unsigned char *)"D", 1, (void *)1, NULL);
raxInsert(rt, (unsigned char *)"", 0, NULL, NULL);
raxRemove(rt, (unsigned char *)"D", 1, NULL);
raxFree(rt);
return 0;
}
/* Regression test #4: Github issue #8, iterator does not populate the
* data field after seek in case of exact match. The test case is looks odd
* because it is quite indirect: Seeking "^" will result into seeking
* the element >= "", and since we just added "" an exact match happens,
* however we are using the original one from the bug report, since this
* is quite odd and may later protect against different bugs related to
* storing and fetching the empty string key. */
int test_raxRegressionTest4(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
rax *rt = raxNew();
raxIterator iter;
raxInsert(rt, (unsigned char *)"", 0, (void *)-1, NULL);
void *val = NULL;
raxFind(rt, (unsigned char *)"", 0, &val);
if (val != (void *)-1) {
printf("Regression test 4 failed. Key value mismatch in raxFind()\n");
return 1;
}
raxStart(&iter, rt);
raxSeek(&iter, "^", NULL, 0);
raxNext(&iter);
if (iter.data != (void *)-1) {
printf("Regression test 4 failed. Key value mismatch in raxNext()\n");
return 1;
}
raxStop(&iter);
raxFree(rt);
return 0;
}
/* Less than seek bug when stopping in the middle of a compressed node. */
int test_raxRegressionTest5(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
rax *rax = raxNew();
raxInsert(rax, (unsigned char *)"b", 1, (void *)(long)1, NULL);
raxInsert(rax, (unsigned char *)"by", 2, (void *)(long)2, NULL);
raxInsert(rax, (unsigned char *)"byword", 6, (void *)(long)3, NULL);
raxInsert(rax, (unsigned char *)"f", 1, (void *)(long)4, NULL);
raxInsert(rax, (unsigned char *)"foobar", 6, (void *)(long)5, NULL);
raxInsert(rax, (unsigned char *)"foobar123", 9, (void *)(long)6, NULL);
raxIterator ri;
raxStart(&ri, rax);
raxSeek(&ri, "<", (unsigned char *)"foo", 3);
raxNext(&ri);
if (ri.key_len != 1 || ri.key[0] != 'f') {
printf("Regression test 4 failed. Key value mismatch in raxNext()\n");
return 1;
}
raxStop(&ri);
raxFree(rax);
return 0;
}
/* Seek may not populate iterator data. See issue #25. */
int test_raxRegressionTest6(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
rax *rax = raxNew();
char *key1 = "172.17.141.2/adminguide/v5.0/";
char *key2 = "172.17.141.2/adminguide/v5.0/entitlements-configure.html";
char *seekpoint = "172.17.141.2/adminguide/v5.0/entitlements";
raxInsert(rax, (unsigned char *)key1, strlen(key1), (void *)(long)1234, NULL);
raxInsert(rax, (unsigned char *)key2, strlen(key2), (void *)(long)5678, NULL);
raxIterator ri;
raxStart(&ri, rax);
raxSeek(&ri, "<=", (unsigned char *)seekpoint, strlen(seekpoint));
raxPrev(&ri);
if ((long)ri.data != 1234) {
printf("Regression test 6 failed. Key data not populated.\n");
return 1;
}
raxStop(&ri);
raxFree(rax);
return 0;
}
int test_raxBenchmark(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
if (!(flags & UNIT_TEST_SINGLE)) return 0;
for (int mode = 0; mode < 2; mode++) {
printf("Benchmark with %s keys:\n", (mode == 0) ? "integer" : "alphanumerical");
rax *t = raxNew();
long long start = _ustime();
for (int i = 0; i < 5000000; i++) {
char buf[64];
int len = int2key(buf, sizeof(buf), i, mode);
raxInsert(t, (unsigned char *)buf, len, (void *)(long)i, NULL);
TEST_ASSERT(raxAllocSize(t) == zmalloc_used_memory());
}
printf("Insert: %f\n", (double)(_ustime() - start) / 1000000);
printf("%llu total nodes\n", (unsigned long long)t->numnodes);
printf("%llu total elements\n", (unsigned long long)t->numele);
start = _ustime();
for (int i = 0; i < 5000000; i++) {
char buf[64];
int len = int2key(buf, sizeof(buf), i, mode);
void *data;
if (!raxFind(t, (unsigned char *)buf, len, &data) || data != (void *)(long)i) {
printf("Issue with %s: %p instead of %p\n", buf, data, (void *)(long)i);
}
}
printf("Linear lookup: %f\n", (double)(_ustime() - start) / 1000000);
start = _ustime();
for (int i = 0; i < 5000000; i++) {
char buf[64];
int r = genrand64_int64() % 5000000;
int len = int2key(buf, sizeof(buf), r, mode);
void *data;
if (!raxFind(t, (unsigned char *)buf, len, &data) || data != (void *)(long)r) {
printf("Issue with %s: %p instead of %p\n", buf, data, (void *)(long)r);
}
}
printf("Random lookup: %f\n", (double)(_ustime() - start) / 1000000);
start = _ustime();
for (int i = 0; i < 5000000; i++) {
char buf[64];
int len = int2key(buf, sizeof(buf), i, mode);
buf[i % len] = '!'; /* "!" is never set into keys. */
TEST_ASSERT_MESSAGE("Lookup should have failed", !raxFind(t, (unsigned char *)buf, len, NULL));
}
printf("Failed lookup: %f\n", (double)(_ustime() - start) / 1000000);
start = _ustime();
raxIterator ri;
raxStart(&ri, t);
raxSeek(&ri, "^", NULL, 0);
int iter = 0;
while (raxNext(&ri)) iter++;
TEST_ASSERT_MESSAGE("Iteration is incomplete", iter == 5000000);
raxStop(&ri);
printf("Full iteration: %f\n", (double)(_ustime() - start) / 1000000);
start = _ustime();
for (int i = 0; i < 5000000; i++) {
char buf[64];
int len = int2key(buf, sizeof(buf), i, mode);
int retval = raxRemove(t, (unsigned char *)buf, len, NULL);
TEST_ASSERT(retval == 1);
TEST_ASSERT(raxAllocSize(t) == zmalloc_used_memory());
}
printf("Deletion: %f\n", (double)(_ustime() - start) / 1000000);
printf("%llu total nodes\n", (unsigned long long)t->numnodes);
printf("%llu total elements\n", (unsigned long long)t->numele);
raxFree(t);
}
return 0;
}
/* Compressed nodes can only hold (2^29)-1 characters, so it is important
* to test for keys bigger than this amount, in order to make sure that
* the code to handle this edge case works as expected.
*
* This test is disabled by default because it uses a lot of memory. */
int test_raxHugeKey(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
if (!(flags & UNIT_TEST_LARGE_MEMORY)) return 0;
size_t max_keylen = ((1 << 29) - 1) + 100;
unsigned char *key = zmalloc(max_keylen);
if (key == NULL) goto oom;
memset(key, 'a', max_keylen);
key[10] = 'X';
key[max_keylen - 1] = 'Y';
rax *rax = raxNew();
int retval = raxInsert(rax, (unsigned char *)"aaabbb", 6, (void *)5678L, NULL);
if (retval == 0 && errno == ENOMEM) goto oom;
retval = raxInsert(rax, key, max_keylen, (void *)1234L, NULL);
if (retval == 0 && errno == ENOMEM) goto oom;
void *value1, *value2;
int found1 = raxFind(rax, (unsigned char *)"aaabbb", 6, &value1);
int found2 = raxFind(rax, key, max_keylen, &value2);
zfree(key);
if (!found1 || !found2) {
printf("Huge key test failed on elementhood\n");
return 1;
}
if (value1 != (void *)5678L || value2 != (void *)1234L) {
printf("Huge key test failed\n");
return 1;
}
raxFree(rax);
return 0;
oom:
fprintf(stderr, "Sorry, not enough memory to execute --hugekey test.");
exit(1);
}
int test_raxFuzz(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
if (!(flags & UNIT_TEST_ACCURATE)) return 0;
int errors = 0;
init_genrand64(1234);
for (int i = 0; i < 10; i++) {
double alpha = (double)genrand64_int64() / RAND_MAX;
double beta = 1 - alpha;
if (fuzzTestCluster(genrand64_int64() % 100000000, alpha, beta)) errors++;
}
for (int i = 0; i < 10; i++) {
double alpha = (double)genrand64_int64() / RAND_MAX;
double beta = 1 - alpha;
if (fuzzTest(KEY_INT, genrand64_int64() % 10000, alpha, beta)) errors++;
if (fuzzTest(KEY_UNIQUE_ALPHA, genrand64_int64() % 10000, alpha, beta)) errors++;
if (fuzzTest(KEY_RANDOM, genrand64_int64() % 10000, alpha, beta)) errors++;
if (fuzzTest(KEY_RANDOM_ALPHA, genrand64_int64() % 10000, alpha, beta)) errors++;
if (fuzzTest(KEY_RANDOM_SMALL_CSET, genrand64_int64() % 10000, alpha, beta)) errors++;
}
size_t numops = 100000, cycles = 3;
while (cycles--) {
if (fuzzTest(KEY_INT, numops, .7, .3)) errors++;
if (fuzzTest(KEY_UNIQUE_ALPHA, numops, .7, .3)) errors++;
if (fuzzTest(KEY_RANDOM, numops, .7, .3)) errors++;
if (fuzzTest(KEY_RANDOM_ALPHA, numops, .7, .3)) errors++;
if (fuzzTest(KEY_RANDOM_SMALL_CSET, numops, .7, .3)) errors++;
numops *= 10;
}
if (fuzzTest(KEY_CHAIN, 1000, .7, .3)) errors++;
printf("Iterator fuzz test: ");
fflush(stdout);
for (int i = 0; i < 100000; i++) {
if (iteratorFuzzTest(KEY_INT, 100)) errors++;
if (iteratorFuzzTest(KEY_UNIQUE_ALPHA, 100)) errors++;
if (iteratorFuzzTest(KEY_RANDOM_ALPHA, 1000)) errors++;
if (iteratorFuzzTest(KEY_RANDOM, 1000)) errors++;
if (i && !(i % 100)) {
printf(".");
if (!(i % 1000)) {
printf("%d%% done", i / 1000);
}
fflush(stdout);
}
}
printf("\n");
if (errors) {
printf("!!! WARNING !!!: %d errors found\n", errors);
} else {
printf("OK! \\o/\n");
}
return !!errors;
}