Merge branch 'gt/unit-test-hashmap'
An existing test of hashmap API has been rewritten with the unit-test framework. * gt/unit-test-hashmap: t: port helper/test-hashmap.c to unit-tests/t-hashmap.cmaint
commit
a3d71f2076
1
Makefile
1
Makefile
|
@ -1335,6 +1335,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
|
||||||
UNIT_TEST_PROGRAMS += t-ctype
|
UNIT_TEST_PROGRAMS += t-ctype
|
||||||
UNIT_TEST_PROGRAMS += t-example-decorate
|
UNIT_TEST_PROGRAMS += t-example-decorate
|
||||||
UNIT_TEST_PROGRAMS += t-hash
|
UNIT_TEST_PROGRAMS += t-hash
|
||||||
|
UNIT_TEST_PROGRAMS += t-hashmap
|
||||||
UNIT_TEST_PROGRAMS += t-mem-pool
|
UNIT_TEST_PROGRAMS += t-mem-pool
|
||||||
UNIT_TEST_PROGRAMS += t-oidmap
|
UNIT_TEST_PROGRAMS += t-oidmap
|
||||||
UNIT_TEST_PROGRAMS += t-oidtree
|
UNIT_TEST_PROGRAMS += t-oidtree
|
||||||
|
|
|
@ -12,11 +12,6 @@ struct test_entry
|
||||||
char key[FLEX_ARRAY];
|
char key[FLEX_ARRAY];
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char *get_value(const struct test_entry *e)
|
|
||||||
{
|
|
||||||
return e->key + strlen(e->key) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int test_entry_cmp(const void *cmp_data,
|
static int test_entry_cmp(const void *cmp_data,
|
||||||
const struct hashmap_entry *eptr,
|
const struct hashmap_entry *eptr,
|
||||||
const struct hashmap_entry *entry_or_key,
|
const struct hashmap_entry *entry_or_key,
|
||||||
|
@ -141,30 +136,16 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)
|
||||||
/*
|
/*
|
||||||
* Read stdin line by line and print result of commands to stdout:
|
* Read stdin line by line and print result of commands to stdout:
|
||||||
*
|
*
|
||||||
* hash key -> strhash(key) memhash(key) strihash(key) memihash(key)
|
|
||||||
* put key value -> NULL / old value
|
|
||||||
* get key -> NULL / value
|
|
||||||
* remove key -> NULL / old value
|
|
||||||
* iterate -> key1 value1\nkey2 value2\n...
|
|
||||||
* size -> tablesize numentries
|
|
||||||
*
|
|
||||||
* perfhashmap method rounds -> test hashmap.[ch] performance
|
* perfhashmap method rounds -> test hashmap.[ch] performance
|
||||||
*/
|
*/
|
||||||
int cmd__hashmap(int argc, const char **argv)
|
int cmd__hashmap(int argc, const char **argv)
|
||||||
{
|
{
|
||||||
struct string_list parts = STRING_LIST_INIT_NODUP;
|
struct string_list parts = STRING_LIST_INIT_NODUP;
|
||||||
struct strbuf line = STRBUF_INIT;
|
struct strbuf line = STRBUF_INIT;
|
||||||
int icase;
|
|
||||||
struct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase);
|
|
||||||
|
|
||||||
/* init hash map */
|
|
||||||
icase = argc > 1 && !strcmp("ignorecase", argv[1]);
|
|
||||||
|
|
||||||
/* process commands from stdin */
|
/* process commands from stdin */
|
||||||
while (strbuf_getline(&line, stdin) != EOF) {
|
while (strbuf_getline(&line, stdin) != EOF) {
|
||||||
char *cmd, *p1, *p2;
|
char *cmd, *p1, *p2;
|
||||||
unsigned int hash = 0;
|
|
||||||
struct test_entry *entry;
|
|
||||||
|
|
||||||
/* break line into command and up to two parameters */
|
/* break line into command and up to two parameters */
|
||||||
string_list_setlen(&parts, 0);
|
string_list_setlen(&parts, 0);
|
||||||
|
@ -180,84 +161,8 @@ int cmd__hashmap(int argc, const char **argv)
|
||||||
cmd = parts.items[0].string;
|
cmd = parts.items[0].string;
|
||||||
p1 = parts.nr >= 1 ? parts.items[1].string : NULL;
|
p1 = parts.nr >= 1 ? parts.items[1].string : NULL;
|
||||||
p2 = parts.nr >= 2 ? parts.items[2].string : NULL;
|
p2 = parts.nr >= 2 ? parts.items[2].string : NULL;
|
||||||
if (p1)
|
|
||||||
hash = icase ? strihash(p1) : strhash(p1);
|
|
||||||
|
|
||||||
if (!strcmp("add", cmd) && p1 && p2) {
|
if (!strcmp("perfhashmap", cmd) && p1 && p2) {
|
||||||
|
|
||||||
/* create entry with key = p1, value = p2 */
|
|
||||||
entry = alloc_test_entry(hash, p1, p2);
|
|
||||||
|
|
||||||
/* add to hashmap */
|
|
||||||
hashmap_add(&map, &entry->ent);
|
|
||||||
|
|
||||||
} else if (!strcmp("put", cmd) && p1 && p2) {
|
|
||||||
|
|
||||||
/* create entry with key = p1, value = p2 */
|
|
||||||
entry = alloc_test_entry(hash, p1, p2);
|
|
||||||
|
|
||||||
/* add / replace entry */
|
|
||||||
entry = hashmap_put_entry(&map, entry, ent);
|
|
||||||
|
|
||||||
/* print and free replaced entry, if any */
|
|
||||||
puts(entry ? get_value(entry) : "NULL");
|
|
||||||
free(entry);
|
|
||||||
|
|
||||||
} else if (!strcmp("get", cmd) && p1) {
|
|
||||||
/* lookup entry in hashmap */
|
|
||||||
entry = hashmap_get_entry_from_hash(&map, hash, p1,
|
|
||||||
struct test_entry, ent);
|
|
||||||
|
|
||||||
/* print result */
|
|
||||||
if (!entry)
|
|
||||||
puts("NULL");
|
|
||||||
hashmap_for_each_entry_from(&map, entry, ent)
|
|
||||||
puts(get_value(entry));
|
|
||||||
|
|
||||||
} else if (!strcmp("remove", cmd) && p1) {
|
|
||||||
|
|
||||||
/* setup static key */
|
|
||||||
struct hashmap_entry key;
|
|
||||||
struct hashmap_entry *rm;
|
|
||||||
hashmap_entry_init(&key, hash);
|
|
||||||
|
|
||||||
/* remove entry from hashmap */
|
|
||||||
rm = hashmap_remove(&map, &key, p1);
|
|
||||||
entry = rm ? container_of(rm, struct test_entry, ent)
|
|
||||||
: NULL;
|
|
||||||
|
|
||||||
/* print result and free entry*/
|
|
||||||
puts(entry ? get_value(entry) : "NULL");
|
|
||||||
free(entry);
|
|
||||||
|
|
||||||
} else if (!strcmp("iterate", cmd)) {
|
|
||||||
struct hashmap_iter iter;
|
|
||||||
|
|
||||||
hashmap_for_each_entry(&map, &iter, entry,
|
|
||||||
ent /* member name */)
|
|
||||||
printf("%s %s\n", entry->key, get_value(entry));
|
|
||||||
|
|
||||||
} else if (!strcmp("size", cmd)) {
|
|
||||||
|
|
||||||
/* print table sizes */
|
|
||||||
printf("%u %u\n", map.tablesize,
|
|
||||||
hashmap_get_size(&map));
|
|
||||||
|
|
||||||
} else if (!strcmp("intern", cmd) && p1) {
|
|
||||||
|
|
||||||
/* test that strintern works */
|
|
||||||
const char *i1 = strintern(p1);
|
|
||||||
const char *i2 = strintern(p1);
|
|
||||||
if (strcmp(i1, p1))
|
|
||||||
printf("strintern(%s) returns %s\n", p1, i1);
|
|
||||||
else if (i1 == p1)
|
|
||||||
printf("strintern(%s) returns input pointer\n", p1);
|
|
||||||
else if (i1 != i2)
|
|
||||||
printf("strintern(%s) != strintern(%s)", i1, i2);
|
|
||||||
else
|
|
||||||
printf("%s\n", i1);
|
|
||||||
|
|
||||||
} else if (!strcmp("perfhashmap", cmd) && p1 && p2) {
|
|
||||||
|
|
||||||
perf_hashmap(atoi(p1), atoi(p2));
|
perf_hashmap(atoi(p1), atoi(p2));
|
||||||
|
|
||||||
|
@ -270,6 +175,5 @@ int cmd__hashmap(int argc, const char **argv)
|
||||||
|
|
||||||
string_list_clear(&parts, 0);
|
string_list_clear(&parts, 0);
|
||||||
strbuf_release(&line);
|
strbuf_release(&line);
|
||||||
hashmap_clear_and_free(&map, struct test_entry, ent);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,260 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
test_description='test hashmap and string hash functions'
|
|
||||||
|
|
||||||
TEST_PASSES_SANITIZE_LEAK=true
|
|
||||||
. ./test-lib.sh
|
|
||||||
|
|
||||||
test_hashmap() {
|
|
||||||
echo "$1" | test-tool hashmap $3 > actual &&
|
|
||||||
echo "$2" > expect &&
|
|
||||||
test_cmp expect actual
|
|
||||||
}
|
|
||||||
|
|
||||||
test_expect_success 'put' '
|
|
||||||
|
|
||||||
test_hashmap "put key1 value1
|
|
||||||
put key2 value2
|
|
||||||
put fooBarFrotz value3
|
|
||||||
put foobarfrotz value4
|
|
||||||
size" "NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
64 4"
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'put (case insensitive)' '
|
|
||||||
|
|
||||||
test_hashmap "put key1 value1
|
|
||||||
put key2 value2
|
|
||||||
put fooBarFrotz value3
|
|
||||||
size" "NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
64 3" ignorecase
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'replace' '
|
|
||||||
|
|
||||||
test_hashmap "put key1 value1
|
|
||||||
put key1 value2
|
|
||||||
put fooBarFrotz value3
|
|
||||||
put fooBarFrotz value4
|
|
||||||
size" "NULL
|
|
||||||
value1
|
|
||||||
NULL
|
|
||||||
value3
|
|
||||||
64 2"
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'replace (case insensitive)' '
|
|
||||||
|
|
||||||
test_hashmap "put key1 value1
|
|
||||||
put Key1 value2
|
|
||||||
put fooBarFrotz value3
|
|
||||||
put foobarfrotz value4
|
|
||||||
size" "NULL
|
|
||||||
value1
|
|
||||||
NULL
|
|
||||||
value3
|
|
||||||
64 2" ignorecase
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'get' '
|
|
||||||
|
|
||||||
test_hashmap "put key1 value1
|
|
||||||
put key2 value2
|
|
||||||
put fooBarFrotz value3
|
|
||||||
put foobarfrotz value4
|
|
||||||
get key1
|
|
||||||
get key2
|
|
||||||
get fooBarFrotz
|
|
||||||
get notInMap" "NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
value1
|
|
||||||
value2
|
|
||||||
value3
|
|
||||||
NULL"
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'get (case insensitive)' '
|
|
||||||
|
|
||||||
test_hashmap "put key1 value1
|
|
||||||
put key2 value2
|
|
||||||
put fooBarFrotz value3
|
|
||||||
get Key1
|
|
||||||
get keY2
|
|
||||||
get foobarfrotz
|
|
||||||
get notInMap" "NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
value1
|
|
||||||
value2
|
|
||||||
value3
|
|
||||||
NULL" ignorecase
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'add' '
|
|
||||||
|
|
||||||
test_hashmap "add key1 value1
|
|
||||||
add key1 value2
|
|
||||||
add fooBarFrotz value3
|
|
||||||
add fooBarFrotz value4
|
|
||||||
get key1
|
|
||||||
get fooBarFrotz
|
|
||||||
get notInMap" "value2
|
|
||||||
value1
|
|
||||||
value4
|
|
||||||
value3
|
|
||||||
NULL"
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'add (case insensitive)' '
|
|
||||||
|
|
||||||
test_hashmap "add key1 value1
|
|
||||||
add Key1 value2
|
|
||||||
add fooBarFrotz value3
|
|
||||||
add foobarfrotz value4
|
|
||||||
get key1
|
|
||||||
get Foobarfrotz
|
|
||||||
get notInMap" "value2
|
|
||||||
value1
|
|
||||||
value4
|
|
||||||
value3
|
|
||||||
NULL" ignorecase
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'remove' '
|
|
||||||
|
|
||||||
test_hashmap "put key1 value1
|
|
||||||
put key2 value2
|
|
||||||
put fooBarFrotz value3
|
|
||||||
remove key1
|
|
||||||
remove key2
|
|
||||||
remove notInMap
|
|
||||||
size" "NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
value1
|
|
||||||
value2
|
|
||||||
NULL
|
|
||||||
64 1"
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'remove (case insensitive)' '
|
|
||||||
|
|
||||||
test_hashmap "put key1 value1
|
|
||||||
put key2 value2
|
|
||||||
put fooBarFrotz value3
|
|
||||||
remove Key1
|
|
||||||
remove keY2
|
|
||||||
remove notInMap
|
|
||||||
size" "NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
value1
|
|
||||||
value2
|
|
||||||
NULL
|
|
||||||
64 1" ignorecase
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'iterate' '
|
|
||||||
test-tool hashmap >actual.raw <<-\EOF &&
|
|
||||||
put key1 value1
|
|
||||||
put key2 value2
|
|
||||||
put fooBarFrotz value3
|
|
||||||
iterate
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat >expect <<-\EOF &&
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
fooBarFrotz value3
|
|
||||||
key1 value1
|
|
||||||
key2 value2
|
|
||||||
EOF
|
|
||||||
|
|
||||||
sort <actual.raw >actual &&
|
|
||||||
test_cmp expect actual
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'iterate (case insensitive)' '
|
|
||||||
test-tool hashmap ignorecase >actual.raw <<-\EOF &&
|
|
||||||
put key1 value1
|
|
||||||
put key2 value2
|
|
||||||
put fooBarFrotz value3
|
|
||||||
iterate
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat >expect <<-\EOF &&
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
fooBarFrotz value3
|
|
||||||
key1 value1
|
|
||||||
key2 value2
|
|
||||||
EOF
|
|
||||||
|
|
||||||
sort <actual.raw >actual &&
|
|
||||||
test_cmp expect actual
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'grow / shrink' '
|
|
||||||
|
|
||||||
rm -f in &&
|
|
||||||
rm -f expect &&
|
|
||||||
for n in $(test_seq 51)
|
|
||||||
do
|
|
||||||
echo put key$n value$n >> in &&
|
|
||||||
echo NULL >> expect || return 1
|
|
||||||
done &&
|
|
||||||
echo size >> in &&
|
|
||||||
echo 64 51 >> expect &&
|
|
||||||
echo put key52 value52 >> in &&
|
|
||||||
echo NULL >> expect &&
|
|
||||||
echo size >> in &&
|
|
||||||
echo 256 52 >> expect &&
|
|
||||||
for n in $(test_seq 12)
|
|
||||||
do
|
|
||||||
echo remove key$n >> in &&
|
|
||||||
echo value$n >> expect || return 1
|
|
||||||
done &&
|
|
||||||
echo size >> in &&
|
|
||||||
echo 256 40 >> expect &&
|
|
||||||
echo remove key40 >> in &&
|
|
||||||
echo value40 >> expect &&
|
|
||||||
echo size >> in &&
|
|
||||||
echo 64 39 >> expect &&
|
|
||||||
test-tool hashmap <in >out &&
|
|
||||||
test_cmp expect out
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'string interning' '
|
|
||||||
|
|
||||||
test_hashmap "intern value1
|
|
||||||
intern Value1
|
|
||||||
intern value2
|
|
||||||
intern value2
|
|
||||||
" "value1
|
|
||||||
Value1
|
|
||||||
value2
|
|
||||||
value2"
|
|
||||||
|
|
||||||
'
|
|
||||||
|
|
||||||
test_done
|
|
|
@ -0,0 +1,361 @@
|
||||||
|
#include "test-lib.h"
|
||||||
|
#include "hashmap.h"
|
||||||
|
#include "strbuf.h"
|
||||||
|
|
||||||
|
struct test_entry {
|
||||||
|
int padding; /* hashmap entry no longer needs to be the first member */
|
||||||
|
struct hashmap_entry ent;
|
||||||
|
/* key and value as two \0-terminated strings */
|
||||||
|
char key[FLEX_ARRAY];
|
||||||
|
};
|
||||||
|
|
||||||
|
static int test_entry_cmp(const void *cmp_data,
|
||||||
|
const struct hashmap_entry *eptr,
|
||||||
|
const struct hashmap_entry *entry_or_key,
|
||||||
|
const void *keydata)
|
||||||
|
{
|
||||||
|
const unsigned int ignore_case = cmp_data ? *((int *)cmp_data) : 0;
|
||||||
|
const struct test_entry *e1, *e2;
|
||||||
|
const char *key = keydata;
|
||||||
|
|
||||||
|
e1 = container_of(eptr, const struct test_entry, ent);
|
||||||
|
e2 = container_of(entry_or_key, const struct test_entry, ent);
|
||||||
|
|
||||||
|
if (ignore_case)
|
||||||
|
return strcasecmp(e1->key, key ? key : e2->key);
|
||||||
|
else
|
||||||
|
return strcmp(e1->key, key ? key : e2->key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *get_value(const struct test_entry *e)
|
||||||
|
{
|
||||||
|
return e->key + strlen(e->key) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct test_entry *alloc_test_entry(const char *key, const char *value,
|
||||||
|
unsigned int ignore_case)
|
||||||
|
{
|
||||||
|
size_t klen = strlen(key);
|
||||||
|
size_t vlen = strlen(value);
|
||||||
|
unsigned int hash = ignore_case ? strihash(key) : strhash(key);
|
||||||
|
struct test_entry *entry = xmalloc(st_add4(sizeof(*entry), klen, vlen, 2));
|
||||||
|
|
||||||
|
hashmap_entry_init(&entry->ent, hash);
|
||||||
|
memcpy(entry->key, key, klen + 1);
|
||||||
|
memcpy(entry->key + klen + 1, value, vlen + 1);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct test_entry *get_test_entry(struct hashmap *map, const char *key,
|
||||||
|
unsigned int ignore_case)
|
||||||
|
{
|
||||||
|
return hashmap_get_entry_from_hash(
|
||||||
|
map, ignore_case ? strihash(key) : strhash(key), key,
|
||||||
|
struct test_entry, ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int key_val_contains(const char *key_val[][2], char seen[], size_t n,
|
||||||
|
struct test_entry *entry)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
if (!strcmp(entry->key, key_val[i][0]) &&
|
||||||
|
!strcmp(get_value(entry), key_val[i][1])) {
|
||||||
|
if (seen[i])
|
||||||
|
return 2;
|
||||||
|
seen[i] = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setup(void (*f)(struct hashmap *map, unsigned int ignore_case),
|
||||||
|
unsigned int ignore_case)
|
||||||
|
{
|
||||||
|
struct hashmap map = HASHMAP_INIT(test_entry_cmp, &ignore_case);
|
||||||
|
|
||||||
|
f(&map, ignore_case);
|
||||||
|
hashmap_clear_and_free(&map, struct test_entry, ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void t_replace(struct hashmap *map, unsigned int ignore_case)
|
||||||
|
{
|
||||||
|
struct test_entry *entry;
|
||||||
|
|
||||||
|
entry = alloc_test_entry("key1", "value1", ignore_case);
|
||||||
|
check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
|
||||||
|
|
||||||
|
entry = alloc_test_entry(ignore_case ? "Key1" : "key1", "value2",
|
||||||
|
ignore_case);
|
||||||
|
entry = hashmap_put_entry(map, entry, ent);
|
||||||
|
if (check(entry != NULL))
|
||||||
|
check_str(get_value(entry), "value1");
|
||||||
|
free(entry);
|
||||||
|
|
||||||
|
entry = alloc_test_entry("fooBarFrotz", "value3", ignore_case);
|
||||||
|
check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
|
||||||
|
|
||||||
|
entry = alloc_test_entry(ignore_case ? "FOObarFrotz" : "fooBarFrotz",
|
||||||
|
"value4", ignore_case);
|
||||||
|
entry = hashmap_put_entry(map, entry, ent);
|
||||||
|
if (check(entry != NULL))
|
||||||
|
check_str(get_value(entry), "value3");
|
||||||
|
free(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void t_get(struct hashmap *map, unsigned int ignore_case)
|
||||||
|
{
|
||||||
|
struct test_entry *entry;
|
||||||
|
const char *key_val[][2] = { { "key1", "value1" },
|
||||||
|
{ "key2", "value2" },
|
||||||
|
{ "fooBarFrotz", "value3" },
|
||||||
|
{ ignore_case ? "key4" : "foobarfrotz",
|
||||||
|
"value4" } };
|
||||||
|
const char *query[][2] = {
|
||||||
|
{ ignore_case ? "Key1" : "key1", "value1" },
|
||||||
|
{ ignore_case ? "keY2" : "key2", "value2" },
|
||||||
|
{ ignore_case ? "FOObarFrotz" : "fooBarFrotz", "value3" },
|
||||||
|
{ ignore_case ? "FOObarFrotz" : "foobarfrotz",
|
||||||
|
ignore_case ? "value3" : "value4" }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
|
||||||
|
entry = alloc_test_entry(key_val[i][0], key_val[i][1],
|
||||||
|
ignore_case);
|
||||||
|
check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(query); i++) {
|
||||||
|
entry = get_test_entry(map, query[i][0], ignore_case);
|
||||||
|
if (check(entry != NULL))
|
||||||
|
check_str(get_value(entry), query[i][1]);
|
||||||
|
else
|
||||||
|
test_msg("query key: %s", query[i][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
check_pointer_eq(get_test_entry(map, "notInMap", ignore_case), NULL);
|
||||||
|
check_int(map->tablesize, ==, 64);
|
||||||
|
check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void t_add(struct hashmap *map, unsigned int ignore_case)
|
||||||
|
{
|
||||||
|
struct test_entry *entry;
|
||||||
|
const char *key_val[][2] = {
|
||||||
|
{ "key1", "value1" },
|
||||||
|
{ ignore_case ? "Key1" : "key1", "value2" },
|
||||||
|
{ "fooBarFrotz", "value3" },
|
||||||
|
{ ignore_case ? "FOObarFrotz" : "fooBarFrotz", "value4" }
|
||||||
|
};
|
||||||
|
const char *query_keys[] = { "key1", ignore_case ? "FOObarFrotz" :
|
||||||
|
"fooBarFrotz" };
|
||||||
|
char seen[ARRAY_SIZE(key_val)] = { 0 };
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
|
||||||
|
entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
|
||||||
|
hashmap_add(map, &entry->ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(query_keys); i++) {
|
||||||
|
int count = 0;
|
||||||
|
entry = hashmap_get_entry_from_hash(map,
|
||||||
|
ignore_case ? strihash(query_keys[i]) :
|
||||||
|
strhash(query_keys[i]),
|
||||||
|
query_keys[i], struct test_entry, ent);
|
||||||
|
|
||||||
|
hashmap_for_each_entry_from(map, entry, ent)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
if (!check_int((ret = key_val_contains(
|
||||||
|
key_val, seen,
|
||||||
|
ARRAY_SIZE(key_val), entry)),
|
||||||
|
==, 0)) {
|
||||||
|
switch (ret) {
|
||||||
|
case 1:
|
||||||
|
test_msg("found entry was not given in the input\n"
|
||||||
|
" key: %s\n value: %s",
|
||||||
|
entry->key, get_value(entry));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
test_msg("duplicate entry detected\n"
|
||||||
|
" key: %s\n value: %s",
|
||||||
|
entry->key, get_value(entry));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check_int(count, ==, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(seen); i++) {
|
||||||
|
if (!check_int(seen[i], ==, 1))
|
||||||
|
test_msg("following key-val pair was not iterated over:\n"
|
||||||
|
" key: %s\n value: %s",
|
||||||
|
key_val[i][0], key_val[i][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
|
||||||
|
check_pointer_eq(get_test_entry(map, "notInMap", ignore_case), NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void t_remove(struct hashmap *map, unsigned int ignore_case)
|
||||||
|
{
|
||||||
|
struct test_entry *entry, *removed;
|
||||||
|
const char *key_val[][2] = { { "key1", "value1" },
|
||||||
|
{ "key2", "value2" },
|
||||||
|
{ "fooBarFrotz", "value3" } };
|
||||||
|
const char *remove[][2] = { { ignore_case ? "Key1" : "key1", "value1" },
|
||||||
|
{ ignore_case ? "keY2" : "key2", "value2" } };
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
|
||||||
|
entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
|
||||||
|
check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(remove); i++) {
|
||||||
|
entry = alloc_test_entry(remove[i][0], "", ignore_case);
|
||||||
|
removed = hashmap_remove_entry(map, entry, ent, remove[i][0]);
|
||||||
|
if (check(removed != NULL))
|
||||||
|
check_str(get_value(removed), remove[i][1]);
|
||||||
|
free(entry);
|
||||||
|
free(removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = alloc_test_entry("notInMap", "", ignore_case);
|
||||||
|
check_pointer_eq(hashmap_remove_entry(map, entry, ent, "notInMap"), NULL);
|
||||||
|
free(entry);
|
||||||
|
|
||||||
|
check_int(map->tablesize, ==, 64);
|
||||||
|
check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val) - ARRAY_SIZE(remove));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void t_iterate(struct hashmap *map, unsigned int ignore_case)
|
||||||
|
{
|
||||||
|
struct test_entry *entry;
|
||||||
|
struct hashmap_iter iter;
|
||||||
|
const char *key_val[][2] = { { "key1", "value1" },
|
||||||
|
{ "key2", "value2" },
|
||||||
|
{ "fooBarFrotz", "value3" } };
|
||||||
|
char seen[ARRAY_SIZE(key_val)] = { 0 };
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
|
||||||
|
entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
|
||||||
|
check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
hashmap_for_each_entry(map, &iter, entry, ent /* member name */)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
if (!check_int((ret = key_val_contains(key_val, seen,
|
||||||
|
ARRAY_SIZE(key_val),
|
||||||
|
entry)), ==, 0)) {
|
||||||
|
switch (ret) {
|
||||||
|
case 1:
|
||||||
|
test_msg("found entry was not given in the input\n"
|
||||||
|
" key: %s\n value: %s",
|
||||||
|
entry->key, get_value(entry));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
test_msg("duplicate entry detected\n"
|
||||||
|
" key: %s\n value: %s",
|
||||||
|
entry->key, get_value(entry));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(seen); i++) {
|
||||||
|
if (!check_int(seen[i], ==, 1))
|
||||||
|
test_msg("following key-val pair was not iterated over:\n"
|
||||||
|
" key: %s\n value: %s",
|
||||||
|
key_val[i][0], key_val[i][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void t_alloc(struct hashmap *map, unsigned int ignore_case)
|
||||||
|
{
|
||||||
|
struct test_entry *entry, *removed;
|
||||||
|
|
||||||
|
for (int i = 1; i <= 51; i++) {
|
||||||
|
char *key = xstrfmt("key%d", i);
|
||||||
|
char *value = xstrfmt("value%d", i);
|
||||||
|
entry = alloc_test_entry(key, value, ignore_case);
|
||||||
|
check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
|
||||||
|
free(key);
|
||||||
|
free(value);
|
||||||
|
}
|
||||||
|
check_int(map->tablesize, ==, 64);
|
||||||
|
check_int(hashmap_get_size(map), ==, 51);
|
||||||
|
|
||||||
|
entry = alloc_test_entry("key52", "value52", ignore_case);
|
||||||
|
check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
|
||||||
|
check_int(map->tablesize, ==, 256);
|
||||||
|
check_int(hashmap_get_size(map), ==, 52);
|
||||||
|
|
||||||
|
for (int i = 1; i <= 12; i++) {
|
||||||
|
char *key = xstrfmt("key%d", i);
|
||||||
|
char *value = xstrfmt("value%d", i);
|
||||||
|
|
||||||
|
entry = alloc_test_entry(key, "", ignore_case);
|
||||||
|
removed = hashmap_remove_entry(map, entry, ent, key);
|
||||||
|
if (check(removed != NULL))
|
||||||
|
check_str(value, get_value(removed));
|
||||||
|
free(key);
|
||||||
|
free(value);
|
||||||
|
free(entry);
|
||||||
|
free(removed);
|
||||||
|
}
|
||||||
|
check_int(map->tablesize, ==, 256);
|
||||||
|
check_int(hashmap_get_size(map), ==, 40);
|
||||||
|
|
||||||
|
entry = alloc_test_entry("key40", "", ignore_case);
|
||||||
|
removed = hashmap_remove_entry(map, entry, ent, "key40");
|
||||||
|
if (check(removed != NULL))
|
||||||
|
check_str("value40", get_value(removed));
|
||||||
|
check_int(map->tablesize, ==, 64);
|
||||||
|
check_int(hashmap_get_size(map), ==, 39);
|
||||||
|
free(entry);
|
||||||
|
free(removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void t_intern(struct hashmap *map, unsigned int ignore_case)
|
||||||
|
{
|
||||||
|
const char *values[] = { "value1", "Value1", "value2", "value2" };
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(values); i++) {
|
||||||
|
const char *i1 = strintern(values[i]);
|
||||||
|
const char *i2 = strintern(values[i]);
|
||||||
|
|
||||||
|
if (!check(!strcmp(i1, values[i])))
|
||||||
|
test_msg("strintern(%s) returns %s\n", values[i], i1);
|
||||||
|
else if (!check(i1 != values[i]))
|
||||||
|
test_msg("strintern(%s) returns input pointer\n",
|
||||||
|
values[i]);
|
||||||
|
else if (!check_pointer_eq(i1, i2))
|
||||||
|
test_msg("address('%s') != address('%s'), so strintern('%s') != strintern('%s')",
|
||||||
|
i1, i2, values[i], values[i]);
|
||||||
|
else
|
||||||
|
check_str(i1, values[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmd_main(int argc UNUSED, const char **argv UNUSED)
|
||||||
|
{
|
||||||
|
TEST(setup(t_replace, 0), "replace works");
|
||||||
|
TEST(setup(t_replace, 1), "replace (case insensitive) works");
|
||||||
|
TEST(setup(t_get, 0), "get works");
|
||||||
|
TEST(setup(t_get, 1), "get (case insensitive) works");
|
||||||
|
TEST(setup(t_add, 0), "add works");
|
||||||
|
TEST(setup(t_add, 1), "add (case insensitive) works");
|
||||||
|
TEST(setup(t_remove, 0), "remove works");
|
||||||
|
TEST(setup(t_remove, 1), "remove (case insensitive) works");
|
||||||
|
TEST(setup(t_iterate, 0), "iterate works");
|
||||||
|
TEST(setup(t_iterate, 1), "iterate (case insensitive) works");
|
||||||
|
TEST(setup(t_alloc, 0), "grow / shrink works");
|
||||||
|
TEST(setup(t_intern, 0), "string interning works");
|
||||||
|
return test_done();
|
||||||
|
}
|
Loading…
Reference in New Issue