rax-radix-tree 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +574 -0
- package/benchmark/01.match-equals.js +21 -0
- package/benchmark/02.match-suffix.js +20 -0
- package/benchmark/03.match-prefix.js +28 -0
- package/benchmark/04.match-equals-prefix-suffix.js +36 -0
- package/benchmark/05.match-hosts.js +25 -0
- package/benchmark/06.match-wildcard-hosts.js +25 -0
- package/benchmark/07.match-remote-address.js +40 -0
- package/benchmark/08.match-exprs-headers.js +50 -0
- package/benchmark/09.match-exprs-args.js +50 -0
- package/benchmark/run.sh +3 -0
- package/benchmark/utility.js +56 -0
- package/binding.gyp +14 -0
- package/http-radix-tree.js +150 -0
- package/index.js +5 -0
- package/package.json +38 -0
- package/radix-tree.js +284 -0
- package/src/radix_tree.c +169 -0
- package/src/radix_tree.h +39 -0
- package/src/rax/COPYING +9 -0
- package/src/rax/Makefile +26 -0
- package/src/rax/README.md +488 -0
- package/src/rax/crc16.c +88 -0
- package/src/rax/rax-oom-test.c +175 -0
- package/src/rax/rax-test.c +1065 -0
- package/src/rax/rax.c +1996 -0
- package/src/rax/rax.h +217 -0
- package/src/rax/rax_malloc.h +43 -0
- package/src/rax/rax_oom_malloc.h +63 -0
- package/src/rax/rc4rand.c +77 -0
- package/src/rax/rc4rand.h +9 -0
- package/src/rax_wrapper.cpp +238 -0
- package/test/00.path.test.js +84 -0
- package/test/10.tree.test.js +217 -0
- package/test/20.http-tree.test.js +316 -0
|
@@ -0,0 +1,1065 @@
|
|
|
1
|
+
/* Rax -- A radix tree implementation.
|
|
2
|
+
*
|
|
3
|
+
* Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Redistribution and use in source and binary forms, with or without
|
|
7
|
+
* modification, are permitted provided that the following conditions are met:
|
|
8
|
+
*
|
|
9
|
+
* * Redistributions of source code must retain the above copyright notice,
|
|
10
|
+
* this list of conditions and the following disclaimer.
|
|
11
|
+
* * Redistributions in binary form must reproduce the above copyright
|
|
12
|
+
* notice, this list of conditions and the following disclaimer in the
|
|
13
|
+
* documentation and/or other materials provided with the distribution.
|
|
14
|
+
* * Neither the name of Redis nor the names of its contributors may be used
|
|
15
|
+
* to endorse or promote products derived from this software without
|
|
16
|
+
* specific prior written permission.
|
|
17
|
+
*
|
|
18
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
21
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
22
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
23
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
24
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
25
|
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
26
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
27
|
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
28
|
+
* POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
#include <stdio.h>
|
|
32
|
+
#include <stdint.h>
|
|
33
|
+
#include <stdlib.h>
|
|
34
|
+
#include <string.h>
|
|
35
|
+
#include <sys/time.h>
|
|
36
|
+
#include <assert.h>
|
|
37
|
+
#include <errno.h>
|
|
38
|
+
|
|
39
|
+
#include "rax.h"
|
|
40
|
+
#include "rc4rand.h"
|
|
41
|
+
|
|
42
|
+
uint16_t crc16(const char *buf, int len); /* From crc16.c */
|
|
43
|
+
|
|
44
|
+
/* ---------------------------------------------------------------------------
|
|
45
|
+
* Simple hash table implementation, no rehashing, just chaining. This is
|
|
46
|
+
* used in order to test the radix tree implementation against something that
|
|
47
|
+
* will always "tell the truth" :-) */
|
|
48
|
+
|
|
49
|
+
#define HT_TABLE_SIZE 100000 /* This is huge but we want it fast enough without
|
|
50
|
+
* reahshing needed. */
|
|
51
|
+
typedef struct htNode {
|
|
52
|
+
uint64_t keylen;
|
|
53
|
+
unsigned char *key;
|
|
54
|
+
void *data;
|
|
55
|
+
struct htNode *next;
|
|
56
|
+
} htNode;
|
|
57
|
+
|
|
58
|
+
typedef struct ht {
|
|
59
|
+
uint64_t numele;
|
|
60
|
+
htNode *table[HT_TABLE_SIZE];
|
|
61
|
+
} hashtable;
|
|
62
|
+
|
|
63
|
+
/* Create a new hash table. */
|
|
64
|
+
hashtable *htNew(void) {
|
|
65
|
+
hashtable *ht = calloc(1,sizeof(*ht));
|
|
66
|
+
ht->numele = 0;
|
|
67
|
+
return ht;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* djb2 hash function. */
|
|
71
|
+
uint32_t htHash(unsigned char *s, size_t len) {
|
|
72
|
+
uint32_t hash = 5381;
|
|
73
|
+
for (size_t i = 0; i < len; i++)
|
|
74
|
+
hash = hash * 33 + s[i];
|
|
75
|
+
return hash % HT_TABLE_SIZE;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Low level hash table lookup function. */
|
|
79
|
+
htNode *htRawLookup(hashtable *t, unsigned char *s, size_t len, uint32_t *hash, htNode ***parentlink) {
|
|
80
|
+
uint32_t h = htHash(s,len);
|
|
81
|
+
if (hash) *hash = h;
|
|
82
|
+
htNode *n = t->table[h];
|
|
83
|
+
if (parentlink) *parentlink = &t->table[h];
|
|
84
|
+
while(n) {
|
|
85
|
+
if (n->keylen == len && memcmp(n->key,s,len) == 0) return n;
|
|
86
|
+
if (parentlink) *parentlink = &n->next;
|
|
87
|
+
n = n->next;
|
|
88
|
+
}
|
|
89
|
+
return NULL;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Add an elmenet to the hash table, return 1 if the element is new,
|
|
93
|
+
* 0 if it existed and the value was updated to the new one. */
|
|
94
|
+
int htAdd(hashtable *t, unsigned char *s, size_t len, void *data) {
|
|
95
|
+
uint32_t hash;
|
|
96
|
+
htNode *n = htRawLookup(t,s,len,&hash,NULL);
|
|
97
|
+
|
|
98
|
+
if (!n) {
|
|
99
|
+
n = malloc(sizeof(*n));
|
|
100
|
+
n->key = malloc(len);
|
|
101
|
+
memcpy(n->key,s,len);
|
|
102
|
+
n->keylen = len;
|
|
103
|
+
n->data = data;
|
|
104
|
+
n->next = t->table[hash];
|
|
105
|
+
t->table[hash] = n;
|
|
106
|
+
t->numele++;
|
|
107
|
+
return 1;
|
|
108
|
+
} else {
|
|
109
|
+
n->data = data;
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Remove the specified element, returns 1 on success, 0 if the element
|
|
115
|
+
* was not there already. */
|
|
116
|
+
int htRem(hashtable *t, unsigned char *s, size_t len) {
|
|
117
|
+
htNode **parentlink;
|
|
118
|
+
htNode *n = htRawLookup(t,s,len,NULL,&parentlink);
|
|
119
|
+
|
|
120
|
+
if (!n) return 0;
|
|
121
|
+
*parentlink = n->next;
|
|
122
|
+
free(n->key);
|
|
123
|
+
free(n);
|
|
124
|
+
t->numele--;
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
void *htNotFound = (void*)"ht-not-found";
|
|
129
|
+
|
|
130
|
+
/* Find an element inside the hash table. Returns htNotFound if the
|
|
131
|
+
* element is not there, otherwise returns the associated value. */
|
|
132
|
+
void *htFind(hashtable *t, unsigned char *s, size_t len) {
|
|
133
|
+
htNode *n = htRawLookup(t,s,len,NULL,NULL);
|
|
134
|
+
if (!n) return htNotFound;
|
|
135
|
+
return n->data;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Free the whole hash table including all the linked nodes. */
|
|
139
|
+
void htFree(hashtable *ht) {
|
|
140
|
+
for (int j = 0; j < HT_TABLE_SIZE; j++) {
|
|
141
|
+
htNode *next = ht->table[j];
|
|
142
|
+
while(next) {
|
|
143
|
+
htNode *this = next;
|
|
144
|
+
next = this->next;
|
|
145
|
+
free(this->key);
|
|
146
|
+
free(this);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
free(ht);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* --------------------------------------------------------------------------
|
|
153
|
+
* Utility functions to generate keys, check time usage and so forth.
|
|
154
|
+
* -------------------------------------------------------------------------*/
|
|
155
|
+
|
|
156
|
+
/* This is a simple Feistel network in order to turn every possible
|
|
157
|
+
* uint32_t input into another "randomly" looking uint32_t. It is a
|
|
158
|
+
* one to one map so there are no repetitions. */
|
|
159
|
+
static uint32_t int2int(uint32_t input) {
|
|
160
|
+
uint16_t l = input & 0xffff;
|
|
161
|
+
uint16_t r = input >> 16;
|
|
162
|
+
for (int i = 0; i < 8; i++) {
|
|
163
|
+
uint16_t nl = r;
|
|
164
|
+
uint16_t F = (((r * 31) + (r >> 5) + 7 * 371) ^ r) & 0xffff;
|
|
165
|
+
r = l ^ F;
|
|
166
|
+
l = nl;
|
|
167
|
+
}
|
|
168
|
+
return (r<<16)|l;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Turn an uint32_t integer into an alphanumerical key and return its
|
|
172
|
+
* length. This function is used in order to generate keys that have
|
|
173
|
+
* a large charset, so that the radix tree can be testsed with many
|
|
174
|
+
* children per node. */
|
|
175
|
+
static size_t int2alphakey(char *s, size_t maxlen, uint32_t i) {
|
|
176
|
+
const char *set = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
177
|
+
"abcdefghijklmnopqrstuvwxyz"
|
|
178
|
+
"0123456789";
|
|
179
|
+
const size_t setlen = 62;
|
|
180
|
+
|
|
181
|
+
if (maxlen == 0) return 0;
|
|
182
|
+
maxlen--; /* Space for null term char. */
|
|
183
|
+
size_t len = 0;
|
|
184
|
+
while(len < maxlen) {
|
|
185
|
+
s[len++] = set[i%setlen];
|
|
186
|
+
i /= setlen;
|
|
187
|
+
if (i == 0) break;
|
|
188
|
+
}
|
|
189
|
+
s[len] = '\0';
|
|
190
|
+
return len;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Return the UNIX time in microseconds */
|
|
194
|
+
long long ustime(void) {
|
|
195
|
+
struct timeval tv;
|
|
196
|
+
long long ust;
|
|
197
|
+
|
|
198
|
+
gettimeofday(&tv, NULL);
|
|
199
|
+
ust = ((long long)tv.tv_sec)*1000000;
|
|
200
|
+
ust += tv.tv_usec;
|
|
201
|
+
return ust;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Turn the integer 'i' into a key according to 'mode'.
|
|
205
|
+
* KEY_INT: Just represents the integer as a string.
|
|
206
|
+
* KEY_UNIQUE_ALPHA: Turn it into a random-looking alphanumerical string
|
|
207
|
+
* according to the int2alphakey() function, so that
|
|
208
|
+
* at every integer is mapped a different string.
|
|
209
|
+
* KEY_RANDOM: Totally random string up to maxlen bytes.
|
|
210
|
+
* KEY_RANDOM_ALPHA: Alphanumerical random string up to maxlen bytes.
|
|
211
|
+
* KEY_RANDOM_SMALL_CSET: Small charset random strings.
|
|
212
|
+
* KEY_CHAIN: 'i' times the character "A". */
|
|
213
|
+
#define KEY_INT 0
|
|
214
|
+
#define KEY_UNIQUE_ALPHA 1
|
|
215
|
+
#define KEY_RANDOM 2
|
|
216
|
+
#define KEY_RANDOM_ALPHA 3
|
|
217
|
+
#define KEY_RANDOM_SMALL_CSET 4
|
|
218
|
+
#define KEY_CHAIN 5
|
|
219
|
+
static size_t int2key(char *s, size_t maxlen, uint32_t i, int mode) {
|
|
220
|
+
if (mode == KEY_INT) {
|
|
221
|
+
return snprintf(s,maxlen,"%lu",(unsigned long)i);
|
|
222
|
+
} else if (mode == KEY_UNIQUE_ALPHA) {
|
|
223
|
+
if (maxlen > 16) maxlen = 16;
|
|
224
|
+
i = int2int(i);
|
|
225
|
+
return int2alphakey(s,maxlen,i);
|
|
226
|
+
} else if (mode == KEY_RANDOM) {
|
|
227
|
+
if (maxlen > 16) maxlen = 16;
|
|
228
|
+
int r = rc4rand() % maxlen;
|
|
229
|
+
for (int i = 0; i < r; i++) s[i] = rc4rand()&0xff;
|
|
230
|
+
return r;
|
|
231
|
+
} else if (mode == KEY_RANDOM_ALPHA) {
|
|
232
|
+
if (maxlen > 16) maxlen = 16;
|
|
233
|
+
int r = rc4rand() % maxlen;
|
|
234
|
+
for (int i = 0; i < r; i++) s[i] = 'A'+rc4rand()%('z'-'A'+1);
|
|
235
|
+
return r;
|
|
236
|
+
} else if (mode == KEY_RANDOM_SMALL_CSET) {
|
|
237
|
+
if (maxlen > 16) maxlen = 16;
|
|
238
|
+
int r = rc4rand() % maxlen;
|
|
239
|
+
for (int i = 0; i < r; i++) s[i] = 'A'+rc4rand()%4;
|
|
240
|
+
return r;
|
|
241
|
+
} else if (mode == KEY_CHAIN) {
|
|
242
|
+
if (i > maxlen) i = maxlen;
|
|
243
|
+
memset(s,'A',i);
|
|
244
|
+
return i;
|
|
245
|
+
} else {
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* -------------------------------------------------------------------------- */
|
|
251
|
+
|
|
252
|
+
/* Perform a fuzz test, returns 0 on success, 1 on error. */
|
|
253
|
+
int fuzzTest(int keymode, size_t count, double addprob, double remprob) {
|
|
254
|
+
hashtable *ht = htNew();
|
|
255
|
+
rax *rax = raxNew();
|
|
256
|
+
|
|
257
|
+
printf("Fuzz test in mode %d [%zu]: ", keymode, count);
|
|
258
|
+
fflush(stdout);
|
|
259
|
+
|
|
260
|
+
/* Perform random operations on both the dictionaries. */
|
|
261
|
+
for (size_t i = 0; i < count; i++) {
|
|
262
|
+
unsigned char key[1024];
|
|
263
|
+
uint32_t keylen;
|
|
264
|
+
|
|
265
|
+
/* Insert element. */
|
|
266
|
+
if ((double)rc4rand()/RAND_MAX < addprob) {
|
|
267
|
+
keylen = int2key((char*)key,sizeof(key),i,keymode);
|
|
268
|
+
void *val = (void*)(unsigned long)rc4rand();
|
|
269
|
+
/* Stress NULL values more often, they use a special encoding. */
|
|
270
|
+
if (!(rc4rand() % 100)) val = NULL;
|
|
271
|
+
int retval1 = htAdd(ht,key,keylen,val);
|
|
272
|
+
int retval2 = raxInsert(rax,key,keylen,val,NULL);
|
|
273
|
+
if (retval1 != retval2) {
|
|
274
|
+
printf("Fuzz: key insertion reported mismatching value in HT/RAX\n");
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Remove element. */
|
|
280
|
+
if ((double)rc4rand()/RAND_MAX < remprob) {
|
|
281
|
+
keylen = int2key((char*)key,sizeof(key),i,keymode);
|
|
282
|
+
int retval1 = htRem(ht,key,keylen);
|
|
283
|
+
int retval2 = raxRemove(rax,key,keylen,NULL);
|
|
284
|
+
if (retval1 != retval2) {
|
|
285
|
+
printf("Fuzz: key deletion of '%.*s' reported mismatching "
|
|
286
|
+
"value in HT=%d RAX=%d\n",
|
|
287
|
+
(int)keylen,(char*)key,retval1, retval2);
|
|
288
|
+
printf("%p\n", raxFind(rax,key,keylen));
|
|
289
|
+
printf("%p\n", raxNotFound);
|
|
290
|
+
return 1;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/* Check that count matches. */
|
|
296
|
+
if (ht->numele != raxSize(rax)) {
|
|
297
|
+
printf("Fuzz: HT / RAX keys count mismatch: %lu vs %lu\n",
|
|
298
|
+
(unsigned long) ht->numele,
|
|
299
|
+
(unsigned long) raxSize(rax));
|
|
300
|
+
return 1;
|
|
301
|
+
}
|
|
302
|
+
printf("%lu elements inserted\n", (unsigned long)ht->numele);
|
|
303
|
+
|
|
304
|
+
/* Check that elements match. */
|
|
305
|
+
raxIterator iter;
|
|
306
|
+
raxStart(&iter,rax);
|
|
307
|
+
raxSeek(&iter,"^",NULL,0);
|
|
308
|
+
|
|
309
|
+
size_t numkeys = 0;
|
|
310
|
+
while(raxNext(&iter)) {
|
|
311
|
+
void *val1 = htFind(ht,iter.key,iter.key_len);
|
|
312
|
+
void *val2 = raxFind(rax,iter.key,iter.key_len);
|
|
313
|
+
if (val1 != val2) {
|
|
314
|
+
printf("Fuzz: HT=%p, RAX=%p value do not match "
|
|
315
|
+
"for key %.*s\n",
|
|
316
|
+
val1, val2, (int)iter.key_len,(char*)iter.key);
|
|
317
|
+
return 1;
|
|
318
|
+
}
|
|
319
|
+
numkeys++;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* Check that the iterator reported all the elements. */
|
|
323
|
+
if (ht->numele != numkeys) {
|
|
324
|
+
printf("Fuzz: the iterator reported %lu keys instead of %lu\n",
|
|
325
|
+
(unsigned long) numkeys,
|
|
326
|
+
(unsigned long) ht->numele);
|
|
327
|
+
return 1;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
raxStop(&iter);
|
|
331
|
+
raxFree(rax);
|
|
332
|
+
htFree(ht);
|
|
333
|
+
return 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* Redis Cluster alike fuzz testing.
|
|
337
|
+
*
|
|
338
|
+
* This test simulates the radix tree usage made by Redis Cluster in order
|
|
339
|
+
* to maintain the hash slot -> keys mappig. The keys are alphanumerical
|
|
340
|
+
* but the first two bytes that are binary (and are the key hashed).
|
|
341
|
+
*
|
|
342
|
+
* In this test there is no comparison with the hash table, the only goal
|
|
343
|
+
* is to crash the radix tree implementation, or to trigger Valgrind
|
|
344
|
+
* warnings. */
|
|
345
|
+
int fuzzTestCluster(size_t count, double addprob, double remprob) {
|
|
346
|
+
unsigned char key[128];
|
|
347
|
+
int keylen = 0;
|
|
348
|
+
|
|
349
|
+
printf("Cluster Fuzz test [keys:%zu keylen:%d]: ", count, keylen);
|
|
350
|
+
fflush(stdout);
|
|
351
|
+
|
|
352
|
+
rax *rax = raxNew();
|
|
353
|
+
|
|
354
|
+
/* This is our template to generate keys. The first two bytes will
|
|
355
|
+
* be replaced with the binary redis cluster hash slot. */
|
|
356
|
+
keylen = snprintf((char*)key,sizeof(key),"__geocode:2e68e5df3624");
|
|
357
|
+
char *cset = "0123456789abcdef";
|
|
358
|
+
|
|
359
|
+
for (unsigned long j = 0; j < count; j++) {
|
|
360
|
+
/* Generate a random key by altering our template key. */
|
|
361
|
+
|
|
362
|
+
/* With a given probability, let's use a common prefix so that there
|
|
363
|
+
* is a subset of keys that have an higher percentage of probability
|
|
364
|
+
* of being hit again and again. */
|
|
365
|
+
size_t commonprefix = rc4rand() & 0xf;
|
|
366
|
+
if (commonprefix == 0) memcpy(key+10,"2e68e5",6);
|
|
367
|
+
|
|
368
|
+
/* Alter a random char in the key. */
|
|
369
|
+
int pos = 10+rc4rand()%12;
|
|
370
|
+
key[pos] = cset[rc4rand()%16];
|
|
371
|
+
|
|
372
|
+
/* Compute the Redis Cluster hash slot to set the first two
|
|
373
|
+
* binary bytes of the key. */
|
|
374
|
+
int hashslot = crc16((char*)key,keylen) & 0x3FFF;
|
|
375
|
+
key[0] = (hashslot >> 8) & 0xff;
|
|
376
|
+
key[1] = hashslot & 0xff;
|
|
377
|
+
|
|
378
|
+
/* Insert element. */
|
|
379
|
+
if ((double)rc4rand()/RAND_MAX < addprob) {
|
|
380
|
+
raxInsert(rax,key,keylen,NULL,NULL);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/* Remove element. */
|
|
384
|
+
if ((double)rc4rand()/RAND_MAX < remprob) {
|
|
385
|
+
raxRemove(rax,key,keylen,NULL);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
size_t finalkeys = raxSize(rax);
|
|
389
|
+
raxFree(rax);
|
|
390
|
+
printf("ok with %zu final keys\n",finalkeys);
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/* Iterator fuzz testing. Compared the items returned by the Rax iterator with
|
|
395
|
+
* a C implementation obtained by sorting the inserted strings in a linear
|
|
396
|
+
* array. */
|
|
397
|
+
typedef struct arrayItem {
|
|
398
|
+
unsigned char *key;
|
|
399
|
+
size_t key_len;
|
|
400
|
+
} arrayItem;
|
|
401
|
+
|
|
402
|
+
/* Utility functions used with qsort() in order to sort the array of strings
|
|
403
|
+
* in the same way Rax sorts keys (which is, lexicographically considering
|
|
404
|
+
* every byte an unsigned integer. */
|
|
405
|
+
int compareAB(const unsigned char *keya, size_t lena, const unsigned char *keyb, size_t lenb) {
|
|
406
|
+
size_t minlen = (lena <= lenb) ? lena : lenb;
|
|
407
|
+
int retval = memcmp(keya,keyb,minlen);
|
|
408
|
+
if (lena == lenb || retval != 0) return retval;
|
|
409
|
+
return (lena > lenb) ? 1 : -1;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
int compareArrayItems(const void *aptr, const void *bptr) {
|
|
413
|
+
const arrayItem *a = aptr;
|
|
414
|
+
const arrayItem *b = bptr;
|
|
415
|
+
return compareAB(a->key,a->key_len,b->key,b->key_len);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/* Seek an element in the array, returning the seek index (the index inside the
|
|
419
|
+
* array). If the seek is not possible (== operator and key not found or empty
|
|
420
|
+
* array) -1 is returned. */
|
|
421
|
+
int arraySeek(arrayItem *array, int count, unsigned char *key, size_t len, char *op) {
|
|
422
|
+
if (count == 0) return -1;
|
|
423
|
+
if (op[0] == '^') return 0;
|
|
424
|
+
if (op[0] == '$') return count-1;
|
|
425
|
+
|
|
426
|
+
int eq = 0, lt = 0, gt = 0;
|
|
427
|
+
if (op[1] == '=') eq = 1;
|
|
428
|
+
if (op[0] == '<') lt = 1;
|
|
429
|
+
if (op[0] == '>') gt = 1;
|
|
430
|
+
|
|
431
|
+
int i;
|
|
432
|
+
for (i = 0; i < count; i++) {
|
|
433
|
+
int cmp = compareAB(array[i].key,array[i].key_len,key,len);
|
|
434
|
+
if (eq && !cmp) return i;
|
|
435
|
+
if (cmp > 0 && gt) return i;
|
|
436
|
+
if (cmp >= 0 && lt) {
|
|
437
|
+
i--;
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (lt && i == count) return count-1;
|
|
442
|
+
if (i < 0 || i >= count) return -1;
|
|
443
|
+
return i;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
int iteratorFuzzTest(int keymode, size_t count) {
|
|
447
|
+
count = rc4rand()%count;
|
|
448
|
+
rax *rax = raxNew();
|
|
449
|
+
arrayItem *array = malloc(sizeof(arrayItem)*count);
|
|
450
|
+
|
|
451
|
+
/* Fill a radix tree and a linear array with some data. */
|
|
452
|
+
unsigned char key[1024];
|
|
453
|
+
size_t j = 0;
|
|
454
|
+
for (size_t i = 0; i < count; i++) {
|
|
455
|
+
uint32_t keylen = int2key((char*)key,sizeof(key),i,keymode);
|
|
456
|
+
void *val = (void*)(unsigned long)htHash(key,keylen);
|
|
457
|
+
|
|
458
|
+
if (raxInsert(rax,key,keylen,val,NULL)) {
|
|
459
|
+
array[j].key = malloc(keylen);
|
|
460
|
+
array[j].key_len = keylen;
|
|
461
|
+
memcpy(array[j].key,key,keylen);
|
|
462
|
+
j++;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
count = raxSize(rax);
|
|
466
|
+
|
|
467
|
+
/* Sort the array. */
|
|
468
|
+
qsort(array,count,sizeof(arrayItem),compareArrayItems);
|
|
469
|
+
|
|
470
|
+
/* Perform a random seek operation. */
|
|
471
|
+
uint32_t keylen = int2key((char*)key,sizeof(key),
|
|
472
|
+
rc4rand()%(count ? count : 1),keymode);
|
|
473
|
+
raxIterator iter;
|
|
474
|
+
raxStart(&iter,rax);
|
|
475
|
+
char *seekops[] = {"==",">=","<=",">","<","^","$"};
|
|
476
|
+
char *seekop = seekops[rc4rand() % 7];
|
|
477
|
+
raxSeek(&iter,seekop,key,keylen);
|
|
478
|
+
int seekidx = arraySeek(array,count,key,keylen,seekop);
|
|
479
|
+
|
|
480
|
+
int next = rc4rand() % 2;
|
|
481
|
+
int iteration = 0;
|
|
482
|
+
while(1) {
|
|
483
|
+
int rax_res;
|
|
484
|
+
int array_res;
|
|
485
|
+
unsigned char *array_key = NULL;
|
|
486
|
+
size_t array_key_len = 0;
|
|
487
|
+
|
|
488
|
+
array_res = (seekidx == -1) ? 0 : 1;
|
|
489
|
+
if (array_res) {
|
|
490
|
+
if (next && seekidx == (signed)count) array_res = 0;
|
|
491
|
+
if (!next && seekidx == -1) array_res = 0;
|
|
492
|
+
if (array_res != 0) {
|
|
493
|
+
array_key = array[seekidx].key;
|
|
494
|
+
array_key_len = array[seekidx].key_len;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (next) {
|
|
499
|
+
rax_res = raxNext(&iter);
|
|
500
|
+
if (array_res) seekidx++;
|
|
501
|
+
} else {
|
|
502
|
+
rax_res = raxPrev(&iter);
|
|
503
|
+
if (array_res) seekidx--;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/* Both the iteratos should agree about EOF. */
|
|
507
|
+
if (array_res != rax_res) {
|
|
508
|
+
printf("Iter fuzz: iterators do not agree about EOF "
|
|
509
|
+
"at iteration %d: "
|
|
510
|
+
"array_more=%d rax_more=%d next=%d\n",
|
|
511
|
+
iteration, array_res, rax_res, next);
|
|
512
|
+
return 1;
|
|
513
|
+
}
|
|
514
|
+
if (array_res == 0) break; /* End of iteration reached. */
|
|
515
|
+
|
|
516
|
+
/* Check that the returned keys are the same. */
|
|
517
|
+
if (iter.key_len != array_key_len ||
|
|
518
|
+
memcmp(iter.key,array_key,iter.key_len))
|
|
519
|
+
{
|
|
520
|
+
printf("Iter fuzz: returned element %d mismatch\n", iteration);
|
|
521
|
+
printf("SEEKOP was %s\n",seekop);
|
|
522
|
+
if (keymode != KEY_RANDOM) {
|
|
523
|
+
printf("\n");
|
|
524
|
+
printf("BUG SEEKING: %s %.*s\n",seekop,keylen,key);
|
|
525
|
+
printf("%.*s (iter) VS %.*s (array) next=%d idx=%d "
|
|
526
|
+
"count=%lu keymode=%d\n",
|
|
527
|
+
(int)iter.key_len, (char*)iter.key,
|
|
528
|
+
(int)array_key_len, (char*)array_key,
|
|
529
|
+
next, seekidx, (unsigned long)count, keymode);
|
|
530
|
+
if (count < 500) {
|
|
531
|
+
printf("\n");
|
|
532
|
+
for (unsigned int j = 0; j < count; j++) {
|
|
533
|
+
printf("%d) '%.*s'\n",j,
|
|
534
|
+
(int)array[j].key_len,
|
|
535
|
+
array[j].key);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
exit(1);
|
|
539
|
+
}
|
|
540
|
+
return 1;
|
|
541
|
+
}
|
|
542
|
+
iteration++;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
for (unsigned int i = 0; i < count; i++) free(array[i].key);
|
|
546
|
+
free(array);
|
|
547
|
+
raxStop(&iter);
|
|
548
|
+
raxFree(rax);
|
|
549
|
+
return 0;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/* Test the random walk function. */
|
|
553
|
+
int randomWalkTest(void) {
|
|
554
|
+
rax *t = raxNew();
|
|
555
|
+
char *toadd[] = {"alligator","alien","baloon","chromodynamic","romane","romanus","romulus","rubens","ruber","rubicon","rubicundus","all","rub","ba",NULL};
|
|
556
|
+
|
|
557
|
+
long numele;
|
|
558
|
+
for (numele = 0; toadd[numele] != NULL; numele++) {
|
|
559
|
+
raxInsert(t,(unsigned char*)toadd[numele],
|
|
560
|
+
strlen(toadd[numele]),(void*)numele,NULL);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
raxIterator iter;
|
|
564
|
+
raxStart(&iter,t);
|
|
565
|
+
raxSeek(&iter,"^",NULL,0);
|
|
566
|
+
int maxloops = 100000;
|
|
567
|
+
while(raxRandomWalk(&iter,0) && maxloops--) {
|
|
568
|
+
int nulls = 0;
|
|
569
|
+
for (long i = 0; i < numele; i++) {
|
|
570
|
+
if (toadd[i] == NULL) {
|
|
571
|
+
nulls++;
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (strlen(toadd[i]) == iter.key_len &&
|
|
575
|
+
memcmp(toadd[i],iter.key,iter.key_len) == 0)
|
|
576
|
+
{
|
|
577
|
+
toadd[i] = NULL;
|
|
578
|
+
nulls++;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (nulls == numele) break;
|
|
582
|
+
}
|
|
583
|
+
if (maxloops == 0) {
|
|
584
|
+
printf("randomWalkTest() is unable to report all the elements "
|
|
585
|
+
"after 100k iterations!\n");
|
|
586
|
+
return 1;
|
|
587
|
+
}
|
|
588
|
+
raxStop(&iter);
|
|
589
|
+
raxFree(t);
|
|
590
|
+
return 0;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
int iteratorUnitTests(void) {
|
|
594
|
+
rax *t = raxNew();
|
|
595
|
+
char *toadd[] = {"alligator","alien","baloon","chromodynamic","romane","romanus","romulus","rubens","ruber","rubicon","rubicundus","all","rub","ba",NULL};
|
|
596
|
+
|
|
597
|
+
for (int x = 0; x < 10000; x++) rc4rand();
|
|
598
|
+
|
|
599
|
+
long items = 0;
|
|
600
|
+
while(toadd[items] != NULL) items++;
|
|
601
|
+
|
|
602
|
+
for (long i = 0; i < items; i++)
|
|
603
|
+
raxInsert(t,(unsigned char*)toadd[i],strlen(toadd[i]),(void*)i,NULL);
|
|
604
|
+
|
|
605
|
+
raxIterator iter;
|
|
606
|
+
raxStart(&iter,t);
|
|
607
|
+
|
|
608
|
+
struct {
|
|
609
|
+
char *seek;
|
|
610
|
+
size_t seeklen;
|
|
611
|
+
char *seekop;
|
|
612
|
+
char *expected;
|
|
613
|
+
} tests[] = {
|
|
614
|
+
/* Seek value. */ /* Expected result. */
|
|
615
|
+
{"rpxxx",5,"<=", "romulus"},
|
|
616
|
+
{"rom",3,">=", "romane"},
|
|
617
|
+
{"rub",3,">=", "rub"},
|
|
618
|
+
{"rub",3,">", "rubens"},
|
|
619
|
+
{"rub",3,"<", "romulus"},
|
|
620
|
+
{"rom",3,">", "romane"},
|
|
621
|
+
{"chro",4,">", "chromodynamic"},
|
|
622
|
+
{"chro",4,"<", "baloon"},
|
|
623
|
+
{"chromz",6,"<", "chromodynamic"},
|
|
624
|
+
{"",0,"^", "alien"},
|
|
625
|
+
{"zorro",5,"<=", "rubicundus"},
|
|
626
|
+
{"zorro",5,"<", "rubicundus"},
|
|
627
|
+
{"zorro",5,"<", "rubicundus"},
|
|
628
|
+
{"",0,"$", "rubicundus"},
|
|
629
|
+
{"ro",2,">=", "romane"},
|
|
630
|
+
{"zo",2,">", NULL},
|
|
631
|
+
{"zo",2,"==", NULL},
|
|
632
|
+
{"romane",6,"==", "romane"}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
for (int i = 0; tests[i].expected != NULL; i++) {
|
|
636
|
+
raxSeek(&iter,tests[i].seekop,(unsigned char*)tests[i].seek,
|
|
637
|
+
tests[i].seeklen);
|
|
638
|
+
int retval = raxNext(&iter);
|
|
639
|
+
|
|
640
|
+
if (tests[i].expected != NULL) {
|
|
641
|
+
if (strlen(tests[i].expected) != iter.key_len ||
|
|
642
|
+
memcmp(tests[i].expected,iter.key,iter.key_len) != 0)
|
|
643
|
+
{
|
|
644
|
+
printf("Iterator unit test error: "
|
|
645
|
+
"test %d, %s expected, %.*s reported\n",
|
|
646
|
+
i, tests[i].expected, (int)iter.key_len,
|
|
647
|
+
(char*)iter.key);
|
|
648
|
+
return 1;
|
|
649
|
+
}
|
|
650
|
+
} else {
|
|
651
|
+
if (retval != 0) {
|
|
652
|
+
printf("Iterator unit test error: "
|
|
653
|
+
"EOF expected in test %d\n", i);
|
|
654
|
+
return 1;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
raxStop(&iter);
|
|
659
|
+
raxFree(t);
|
|
660
|
+
return 0;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/* Test that raxInsert() / raxTryInsert() overwrite semantic
|
|
664
|
+
* works as expected. */
|
|
665
|
+
int tryInsertUnitTests(void) {
|
|
666
|
+
rax *t = raxNew();
|
|
667
|
+
raxInsert(t,(unsigned char*)"FOO",3,(void*)(long)1,NULL);
|
|
668
|
+
void *old, *val;
|
|
669
|
+
raxTryInsert(t,(unsigned char*)"FOO",3,(void*)(long)2,&old);
|
|
670
|
+
if (old != (void*)(long)1) {
|
|
671
|
+
printf("Old value not returned correctly by raxTryInsert(): %p",
|
|
672
|
+
old);
|
|
673
|
+
return 1;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
val = raxFind(t,(unsigned char*)"FOO",3);
|
|
677
|
+
if (val != (void*)(long)1) {
|
|
678
|
+
printf("FOO value mismatch: is %p intead of 1", val);
|
|
679
|
+
return 1;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
raxInsert(t,(unsigned char*)"FOO",3,(void*)(long)2,NULL);
|
|
683
|
+
val = raxFind(t,(unsigned char*)"FOO",3);
|
|
684
|
+
if (val != (void*)(long)2) {
|
|
685
|
+
printf("FOO value mismatch: is %p intead of 2", val);
|
|
686
|
+
return 1;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
raxFree(t);
|
|
690
|
+
return 0;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/* Regression test #1: Iterator wrong element returned after seek. */
|
|
694
|
+
int regtest1(void) {
|
|
695
|
+
rax *rax = raxNew();
|
|
696
|
+
raxInsert(rax,(unsigned char*)"LKE",3,(void*)(long)1,NULL);
|
|
697
|
+
raxInsert(rax,(unsigned char*)"TQ",2,(void*)(long)2,NULL);
|
|
698
|
+
raxInsert(rax,(unsigned char*)"B",1,(void*)(long)3,NULL);
|
|
699
|
+
raxInsert(rax,(unsigned char*)"FY",2,(void*)(long)4,NULL);
|
|
700
|
+
raxInsert(rax,(unsigned char*)"WI",2,(void*)(long)5,NULL);
|
|
701
|
+
|
|
702
|
+
raxIterator iter;
|
|
703
|
+
raxStart(&iter,rax);
|
|
704
|
+
raxSeek(&iter,">",(unsigned char*)"FMP",3);
|
|
705
|
+
if (raxNext(&iter)) {
|
|
706
|
+
if (iter.key_len != 2 ||
|
|
707
|
+
memcmp(iter.key,"FY",2))
|
|
708
|
+
{
|
|
709
|
+
printf("Regression test 1 failed: 'FY' expected, got: '%.*s'\n",
|
|
710
|
+
(int)iter.key_len, (char*)iter.key);
|
|
711
|
+
return 1;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
raxStop(&iter);
|
|
716
|
+
raxFree(rax);
|
|
717
|
+
return 0;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/* Regression test #2: Crash when mixing NULL and not NULL values. */
|
|
721
|
+
int regtest2(void) {
|
|
722
|
+
rax *rt = raxNew();
|
|
723
|
+
raxInsert(rt,(unsigned char *)"a",1,(void *)100,NULL);
|
|
724
|
+
raxInsert(rt,(unsigned char *)"ab",2,(void *)101,NULL);
|
|
725
|
+
raxInsert(rt,(unsigned char *)"abc",3,(void *)NULL,NULL);
|
|
726
|
+
raxInsert(rt,(unsigned char *)"abcd",4,(void *)NULL,NULL);
|
|
727
|
+
raxInsert(rt,(unsigned char *)"abc",3,(void *)102,NULL);
|
|
728
|
+
raxFree(rt);
|
|
729
|
+
return 0;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/* Regression test #3: Wrong access at node value in raxRemoveChild()
|
|
733
|
+
* when iskey == 1 and isnull == 1: the memmove() was performed including
|
|
734
|
+
* the value length regardless of the fact there was no actual value.
|
|
735
|
+
*
|
|
736
|
+
* Note that this test always returns success but will trigger a
|
|
737
|
+
* Valgrind error. */
|
|
738
|
+
int regtest3(void) {
|
|
739
|
+
rax *rt = raxNew();
|
|
740
|
+
raxInsert(rt, (unsigned char *)"D",1,(void*)1,NULL);
|
|
741
|
+
raxInsert(rt, (unsigned char *)"",0,NULL,NULL);
|
|
742
|
+
raxRemove(rt, (unsigned char *)"D",1,NULL);
|
|
743
|
+
raxFree(rt);
|
|
744
|
+
return 0;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/* Regression test #4: Github issue #8, iterator does not populate the
|
|
748
|
+
* data field after seek in case of exact match. The test case is looks odd
|
|
749
|
+
* because it is quite indirect: Seeking "^" will result into seeking
|
|
750
|
+
* the element >= "", and since we just added "" an exact match happens,
|
|
751
|
+
* however we are using the original one from the bug report, since this
|
|
752
|
+
* is quite odd and may later protect against different bugs related to
|
|
753
|
+
* storing and fetching the empty string key. */
|
|
754
|
+
int regtest4(void) {
|
|
755
|
+
rax *rt = raxNew();
|
|
756
|
+
raxIterator iter;
|
|
757
|
+
raxInsert(rt, (unsigned char*)"", 0, (void *)-1, NULL);
|
|
758
|
+
if (raxFind(rt, (unsigned char*)"", 0) != (void *)-1) {
|
|
759
|
+
printf("Regression test 4 failed. Key value mismatch in raxFind()\n");
|
|
760
|
+
return 1;
|
|
761
|
+
}
|
|
762
|
+
raxStart(&iter,rt);
|
|
763
|
+
raxSeek(&iter, "^", NULL, 0);
|
|
764
|
+
raxNext(&iter);
|
|
765
|
+
if (iter.data != (void *)-1) {
|
|
766
|
+
printf("Regression test 4 failed. Key value mismatch in raxNext()\n");
|
|
767
|
+
return 1;
|
|
768
|
+
}
|
|
769
|
+
raxStop(&iter);
|
|
770
|
+
raxFree(rt);
|
|
771
|
+
return 0;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/* Less than seek bug when stopping in the middle of a compressed node. */
|
|
775
|
+
int regtest5(void) {
|
|
776
|
+
rax *rax = raxNew();
|
|
777
|
+
|
|
778
|
+
raxInsert(rax,(unsigned char*)"b",1,(void*)(long)1,NULL);
|
|
779
|
+
raxInsert(rax,(unsigned char*)"ba",2,(void*)(long)2,NULL);
|
|
780
|
+
raxInsert(rax,(unsigned char*)"banana",6,(void*)(long)3,NULL);
|
|
781
|
+
|
|
782
|
+
raxInsert(rax,(unsigned char*)"f",1,(void*)(long)4,NULL);
|
|
783
|
+
raxInsert(rax,(unsigned char*)"foobar",6,(void*)(long)5,NULL);
|
|
784
|
+
raxInsert(rax,(unsigned char*)"foobar123",9,(void*)(long)6,NULL);
|
|
785
|
+
|
|
786
|
+
raxIterator ri;
|
|
787
|
+
raxStart(&ri,rax);
|
|
788
|
+
|
|
789
|
+
raxSeek(&ri,"<",(unsigned char*)"foo",3);
|
|
790
|
+
raxNext(&ri);
|
|
791
|
+
if (ri.key_len != 1 || ri.key[0] != 'f') {
|
|
792
|
+
printf("Regression test 4 failed. Key value mismatch in raxNext()\n");
|
|
793
|
+
return 1;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
raxStop(&ri);
|
|
797
|
+
raxFree(rax);
|
|
798
|
+
return 0;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/* Seek may not populate iterator data. See issue #25. */
|
|
802
|
+
int regtest6(void) {
|
|
803
|
+
rax *rax = raxNew();
|
|
804
|
+
|
|
805
|
+
char *key1 = "172.17.141.2/adminguide/v5.0/";
|
|
806
|
+
char *key2 = "172.17.141.2/adminguide/v5.0/entitlements-configure.html";
|
|
807
|
+
char *seekpoint = "172.17.141.2/adminguide/v5.0/entitlements";
|
|
808
|
+
|
|
809
|
+
raxInsert(rax, (unsigned char*)key1,strlen(key1),(void*)(long)1234, NULL);
|
|
810
|
+
raxInsert(rax, (unsigned char*)key2,strlen(key2),(void*)(long)5678, NULL);
|
|
811
|
+
|
|
812
|
+
raxIterator ri;
|
|
813
|
+
raxStart(&ri,rax);
|
|
814
|
+
raxSeek(&ri,"<=", (unsigned char*)seekpoint, strlen(seekpoint));
|
|
815
|
+
raxPrev(&ri);
|
|
816
|
+
if ((long)ri.data != 1234) {
|
|
817
|
+
printf("Regression test 6 failed. Key data not populated.\n");
|
|
818
|
+
return 1;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
raxStop(&ri);
|
|
822
|
+
raxFree(rax);
|
|
823
|
+
return 0;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
void benchmark(void) {
|
|
827
|
+
for (int mode = 0; mode < 2; mode++) {
|
|
828
|
+
printf("Benchmark with %s keys:\n",
|
|
829
|
+
(mode == 0) ? "integer" : "alphanumerical");
|
|
830
|
+
rax *t = raxNew();
|
|
831
|
+
long long start = ustime();
|
|
832
|
+
for (int i = 0; i < 5000000; i++) {
|
|
833
|
+
char buf[64];
|
|
834
|
+
int len = int2key(buf,sizeof(buf),i,mode);
|
|
835
|
+
raxInsert(t,(unsigned char*)buf,len,(void*)(long)i,NULL);
|
|
836
|
+
}
|
|
837
|
+
printf("Insert: %f\n", (double)(ustime()-start)/1000000);
|
|
838
|
+
printf("%llu total nodes\n", (unsigned long long)t->numnodes);
|
|
839
|
+
printf("%llu total elements\n", (unsigned long long)t->numele);
|
|
840
|
+
|
|
841
|
+
start = ustime();
|
|
842
|
+
for (int i = 0; i < 5000000; i++) {
|
|
843
|
+
char buf[64];
|
|
844
|
+
int len = int2key(buf,sizeof(buf),i,mode);
|
|
845
|
+
void *data = raxFind(t,(unsigned char*)buf,len);
|
|
846
|
+
if (data != (void*)(long)i) {
|
|
847
|
+
printf("Issue with %s: %p instead of %p\n", buf,
|
|
848
|
+
data, (void*)(long)i);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
printf("Linear lookup: %f\n", (double)(ustime()-start)/1000000);
|
|
852
|
+
|
|
853
|
+
start = ustime();
|
|
854
|
+
for (int i = 0; i < 5000000; i++) {
|
|
855
|
+
char buf[64];
|
|
856
|
+
int r = rc4rand() % 5000000;
|
|
857
|
+
int len = int2key(buf,sizeof(buf),r,mode);
|
|
858
|
+
void *data = raxFind(t,(unsigned char*)buf,len);
|
|
859
|
+
if (data != (void*)(long)r) {
|
|
860
|
+
printf("Issue with %s: %p instead of %p\n", buf,
|
|
861
|
+
data, (void*)(long)r);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
printf("Random lookup: %f\n", (double)(ustime()-start)/1000000);
|
|
865
|
+
|
|
866
|
+
start = ustime();
|
|
867
|
+
for (int i = 0; i < 5000000; i++) {
|
|
868
|
+
char buf[64];
|
|
869
|
+
int len = int2key(buf,sizeof(buf),i,mode);
|
|
870
|
+
buf[i%len] = '!'; /* "!" is never set into keys. */
|
|
871
|
+
void *data = raxFind(t,(unsigned char*) buf,len);
|
|
872
|
+
if (data != raxNotFound) {
|
|
873
|
+
printf("** Failed lookup did not reported NOT FOUND!\n");
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
printf("Failed lookup: %f\n", (double)(ustime()-start)/1000000);
|
|
877
|
+
|
|
878
|
+
start = ustime();
|
|
879
|
+
raxIterator ri;
|
|
880
|
+
raxStart(&ri,t);
|
|
881
|
+
raxSeek(&ri,"^",NULL,0);
|
|
882
|
+
int iter = 0;
|
|
883
|
+
while (raxNext(&ri)) iter++;
|
|
884
|
+
if (iter != 5000000) printf("** Warning iteration is incomplete\n");
|
|
885
|
+
raxStop(&ri);
|
|
886
|
+
printf("Full iteration: %f\n", (double)(ustime()-start)/1000000);
|
|
887
|
+
|
|
888
|
+
start = ustime();
|
|
889
|
+
for (int i = 0; i < 5000000; i++) {
|
|
890
|
+
char buf[64];
|
|
891
|
+
int len = int2key(buf,sizeof(buf),i,mode);
|
|
892
|
+
int retval = raxRemove(t,(unsigned char*)buf,len,NULL);
|
|
893
|
+
assert(retval == 1);
|
|
894
|
+
}
|
|
895
|
+
printf("Deletion: %f\n", (double)(ustime()-start)/1000000);
|
|
896
|
+
|
|
897
|
+
printf("%llu total nodes\n", (unsigned long long)t->numnodes);
|
|
898
|
+
printf("%llu total elements\n", (unsigned long long)t->numele);
|
|
899
|
+
raxFree(t);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/* Compressed nodes can only hold (2^29)-1 characters, so it is important
|
|
904
|
+
* to test for keys bigger than this amount, in order to make sure that
|
|
905
|
+
* the code to handle this edge case works as expected.
|
|
906
|
+
*
|
|
907
|
+
* This test is disabled by default because it uses a lot of memory. */
|
|
908
|
+
int testHugeKey(void) {
|
|
909
|
+
size_t max_keylen = ((1<<29)-1) + 100;
|
|
910
|
+
unsigned char *key = malloc(max_keylen);
|
|
911
|
+
if (key == NULL) goto oom;
|
|
912
|
+
|
|
913
|
+
memset(key,'a',max_keylen);
|
|
914
|
+
key[10] = 'X';
|
|
915
|
+
key[max_keylen-1] = 'Y';
|
|
916
|
+
rax *rax = raxNew();
|
|
917
|
+
int retval = raxInsert(rax,(unsigned char*)"aaabbb",6,(void*)5678L,NULL);
|
|
918
|
+
if (retval == 0 && errno == ENOMEM) goto oom;
|
|
919
|
+
retval = raxInsert(rax,key,max_keylen,(void*)1234L,NULL);
|
|
920
|
+
if (retval == 0 && errno == ENOMEM) goto oom;
|
|
921
|
+
void *value1 = raxFind(rax,(unsigned char*)"aaabbb",6);
|
|
922
|
+
void *value2 = raxFind(rax,key,max_keylen);
|
|
923
|
+
if (value1 != (void*)5678L || value2 != (void*)1234L) {
|
|
924
|
+
printf("Huge key test failed\n");
|
|
925
|
+
return 1;
|
|
926
|
+
}
|
|
927
|
+
raxFree(rax);
|
|
928
|
+
return 0;
|
|
929
|
+
|
|
930
|
+
oom:
|
|
931
|
+
fprintf(stderr,"Sorry, not enough memory to execute --hugekey test.");
|
|
932
|
+
exit(1);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
int main(int argc, char **argv) {
|
|
936
|
+
rc4srand(1234);
|
|
937
|
+
|
|
938
|
+
/* Tests to run by default are set here. */
|
|
939
|
+
int do_benchmark = 0;
|
|
940
|
+
int do_units = 1;
|
|
941
|
+
int do_fuzz_cluster = 0;
|
|
942
|
+
int do_fuzz = 1;
|
|
943
|
+
int do_regression = 1;
|
|
944
|
+
int do_hugekey = 0;
|
|
945
|
+
|
|
946
|
+
/* If the user passed arguments, override the tests to run. */
|
|
947
|
+
if (argc > 1) {
|
|
948
|
+
do_benchmark = 0;
|
|
949
|
+
do_units = 0;
|
|
950
|
+
do_fuzz = 0;
|
|
951
|
+
do_regression = 0;
|
|
952
|
+
|
|
953
|
+
for (int i = 1; i < argc; i++) {
|
|
954
|
+
if (!strcmp(argv[i],"--bench")) {
|
|
955
|
+
do_benchmark = 1;
|
|
956
|
+
} else if (!strcmp(argv[i],"--fuzz-cluster")) {
|
|
957
|
+
do_fuzz_cluster = 1;
|
|
958
|
+
} else if (!strcmp(argv[i],"--fuzz")) {
|
|
959
|
+
do_fuzz = 1;
|
|
960
|
+
} else if (!strcmp(argv[i],"--units")) {
|
|
961
|
+
do_units = 1;
|
|
962
|
+
} else if (!strcmp(argv[i],"--regression")) {
|
|
963
|
+
do_regression = 1;
|
|
964
|
+
} else if (!strcmp(argv[i],"--hugekey")) {
|
|
965
|
+
do_hugekey = 1;
|
|
966
|
+
} else {
|
|
967
|
+
fprintf(stderr, "Usage: %s <options>:\n"
|
|
968
|
+
" [--bench (default off)]\n"
|
|
969
|
+
" [--fuzz-cluster] (default off)\n"
|
|
970
|
+
" [--fuzz] (default on)\n"
|
|
971
|
+
" [--units] (default on)\n"
|
|
972
|
+
" [--regression] (default on)\n"
|
|
973
|
+
" [--hugekey (default off)]\n"
|
|
974
|
+
"Without options all the default tests will\n"
|
|
975
|
+
"be executed.\n",
|
|
976
|
+
argv[0]);
|
|
977
|
+
exit(1);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
int errors = 0;
|
|
983
|
+
|
|
984
|
+
if (do_units) {
|
|
985
|
+
printf("Unit tests: "); fflush(stdout);
|
|
986
|
+
if (randomWalkTest()) errors++;
|
|
987
|
+
if (iteratorUnitTests()) errors++;
|
|
988
|
+
if (tryInsertUnitTests()) errors++;
|
|
989
|
+
if (errors == 0) printf("OK\n");
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (do_regression) {
|
|
993
|
+
printf("Performing regression tests: "); fflush(stdout);
|
|
994
|
+
if (regtest1()) errors++;
|
|
995
|
+
if (regtest2()) errors++;
|
|
996
|
+
if (regtest3()) errors++;
|
|
997
|
+
if (regtest4()) errors++;
|
|
998
|
+
if (regtest5()) errors++;
|
|
999
|
+
if (regtest6()) errors++;
|
|
1000
|
+
if (errors == 0) printf("OK\n");
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (do_hugekey) {
|
|
1004
|
+
printf("Performing huge key tests: "); fflush(stdout);
|
|
1005
|
+
if (testHugeKey()) errors++;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (do_fuzz_cluster) {
|
|
1009
|
+
for (int i = 0; i < 10; i++) {
|
|
1010
|
+
double alpha = (double)rc4rand() / RAND_MAX;
|
|
1011
|
+
double beta = 1-alpha;
|
|
1012
|
+
if (fuzzTestCluster(rc4rand()%100000000,alpha,beta)) errors++;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if (do_fuzz) {
|
|
1017
|
+
for (int i = 0; i < 10; i++) {
|
|
1018
|
+
double alpha = (double)rc4rand() / RAND_MAX;
|
|
1019
|
+
double beta = 1-alpha;
|
|
1020
|
+
if (fuzzTest(KEY_INT,rc4rand()%10000,alpha,beta)) errors++;
|
|
1021
|
+
if (fuzzTest(KEY_UNIQUE_ALPHA,rc4rand()%10000,alpha,beta)) errors++;
|
|
1022
|
+
if (fuzzTest(KEY_RANDOM,rc4rand()%10000,alpha,beta)) errors++;
|
|
1023
|
+
if (fuzzTest(KEY_RANDOM_ALPHA,rc4rand()%10000,alpha,beta)) errors++;
|
|
1024
|
+
if (fuzzTest(KEY_RANDOM_SMALL_CSET,rc4rand()%10000,alpha,beta)) errors++;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
size_t numops = 100000, cycles = 3;
|
|
1028
|
+
while(cycles--) {
|
|
1029
|
+
if (fuzzTest(KEY_INT,numops,.7,.3)) errors++;
|
|
1030
|
+
if (fuzzTest(KEY_UNIQUE_ALPHA,numops,.7,.3)) errors++;
|
|
1031
|
+
if (fuzzTest(KEY_RANDOM,numops,.7,.3)) errors++;
|
|
1032
|
+
if (fuzzTest(KEY_RANDOM_ALPHA,numops,.7,.3)) errors++;
|
|
1033
|
+
if (fuzzTest(KEY_RANDOM_SMALL_CSET,numops,.7,.3)) errors++;
|
|
1034
|
+
numops *= 10;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (fuzzTest(KEY_CHAIN,1000,.7,.3)) errors++;
|
|
1038
|
+
printf("Iterator fuzz test: "); fflush(stdout);
|
|
1039
|
+
for (int i = 0; i < 100000; i++) {
|
|
1040
|
+
if (iteratorFuzzTest(KEY_INT,100)) errors++;
|
|
1041
|
+
if (iteratorFuzzTest(KEY_UNIQUE_ALPHA,100)) errors++;
|
|
1042
|
+
if (iteratorFuzzTest(KEY_RANDOM_ALPHA,1000)) errors++;
|
|
1043
|
+
if (iteratorFuzzTest(KEY_RANDOM,1000)) errors++;
|
|
1044
|
+
if (i && !(i % 100)) {
|
|
1045
|
+
printf(".");
|
|
1046
|
+
if (!(i % 1000)) {
|
|
1047
|
+
printf("%d%% done",i/1000);
|
|
1048
|
+
}
|
|
1049
|
+
fflush(stdout);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
printf("\n");
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (do_benchmark) {
|
|
1056
|
+
benchmark();
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (errors) {
|
|
1060
|
+
printf("!!! WARNING !!!: %d errors found\n", errors);
|
|
1061
|
+
} else {
|
|
1062
|
+
printf("OK! \\o/\n");
|
|
1063
|
+
}
|
|
1064
|
+
return errors;
|
|
1065
|
+
}
|