418 lines
14 KiB
C
418 lines
14 KiB
C
|
|
/*
|
||
|
|
* The fixed-capacity hash map and FNV-1a -- src/collections.c, TODO.md 3.6.
|
||
|
|
*
|
||
|
|
* "The single most obviously-missing data structure in the library", by the
|
||
|
|
* TODO's own account: akbasic needed one three times over -- variables,
|
||
|
|
* functions and labels -- and wrote ~130 lines of open-addressed table to get it.
|
||
|
|
*
|
||
|
|
* The behaviour worth testing hardest is the tombstone. Deleting a key that
|
||
|
|
* something else probed past has to leave a marker rather than an empty slot,
|
||
|
|
* because an empty slot ends a probe chain and would make the later key
|
||
|
|
* unreachable while it is still sitting there.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "aksl_capture.h"
|
||
|
|
|
||
|
|
#define CAP 16
|
||
|
|
|
||
|
|
typedef struct SeenLog
|
||
|
|
{
|
||
|
|
int count;
|
||
|
|
char keys[CAP][AKSL_HASHMAP_MAX_KEY];
|
||
|
|
int break_at;
|
||
|
|
} SeenLog;
|
||
|
|
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *record_entry(const char *key, void *value, void *data)
|
||
|
|
{
|
||
|
|
SeenLog *log = NULL;
|
||
|
|
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, key, AKERR_NULLPOINTER, "key");
|
||
|
|
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||
|
|
(void)value;
|
||
|
|
log = (SeenLog *)data;
|
||
|
|
if ( log->count < CAP ) {
|
||
|
|
snprintf(log->keys[log->count], AKSL_HASHMAP_MAX_KEY, "%s", key);
|
||
|
|
}
|
||
|
|
log->count += 1;
|
||
|
|
if ( log->break_at == log->count - 1 ) {
|
||
|
|
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop");
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* FNV-1a */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The reference values are the canonical 32-bit FNV-1a ones: h = 2166136261,
|
||
|
|
* then h = (h XOR byte) * 16777619 for each byte. "a" and "foobar" are the
|
||
|
|
* vectors from the FNV reference material.
|
||
|
|
*/
|
||
|
|
static int test_fnv1a_known_answers(void)
|
||
|
|
{
|
||
|
|
uint32_t h = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strhash_fnv1a("", 0, &h));
|
||
|
|
AKSL_CHECK(h == 2166136261u);
|
||
|
|
AKSL_CHECK_OK(aksl_strhash_fnv1a("a", 1, &h));
|
||
|
|
AKSL_CHECK(h == 0xe40c292cu);
|
||
|
|
AKSL_CHECK_OK(aksl_strhash_fnv1a("foobar", 6, &h));
|
||
|
|
AKSL_CHECK(h == 0xbf9cf968u);
|
||
|
|
|
||
|
|
/* The NUL-terminated form agrees with the length-driven one. */
|
||
|
|
AKSL_CHECK_OK(aksl_strhash_fnv1a_str("foobar", &h));
|
||
|
|
AKSL_CHECK(h == 0xbf9cf968u);
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strhash_fnv1a(NULL, 0, &h), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strhash_fnv1a("a", 1, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strhash_fnv1a_str(NULL, &h), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strhash_fnv1a_str("a", NULL), AKERR_NULLPOINTER);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* High bytes are unsigned here for the same reason they are in djb2. */
|
||
|
|
static int test_fnv1a_high_bit_bytes_are_unsigned(void)
|
||
|
|
{
|
||
|
|
char buf[2] = { (char)0xff, (char)0xfe };
|
||
|
|
uint32_t h = 0;
|
||
|
|
uint32_t expected = 2166136261u;
|
||
|
|
|
||
|
|
expected = (expected ^ 0xffu) * 16777619u;
|
||
|
|
expected = (expected ^ 0xfeu) * 16777619u;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strhash_fnv1a(buf, sizeof(buf), &h));
|
||
|
|
AKSL_CHECK(h == expected);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Put and get */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_put_and_get(void)
|
||
|
|
{
|
||
|
|
aksl_HashEntry slots[CAP];
|
||
|
|
aksl_HashMap map;
|
||
|
|
int a = 1;
|
||
|
|
int b = 2;
|
||
|
|
void *value = NULL;
|
||
|
|
int found = 99;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_init(&map, slots, CAP));
|
||
|
|
AKSL_CHECK(map.count == 0);
|
||
|
|
AKSL_CHECK(map.capacity == CAP);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, "alpha", &a));
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, "beta", &b));
|
||
|
|
AKSL_CHECK(map.count == 2);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_get(&map, "alpha", &value, &found));
|
||
|
|
AKSL_CHECK(found == 1);
|
||
|
|
AKSL_CHECK(value == &a);
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_get(&map, "beta", &value, &found));
|
||
|
|
AKSL_CHECK(found == 1);
|
||
|
|
AKSL_CHECK(value == &b);
|
||
|
|
|
||
|
|
/* A key that is not there is found = 0 and success, not an error: looking
|
||
|
|
* something up and not finding it is the ordinary case for a symbol table. */
|
||
|
|
value = (void *)0x1;
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_get(&map, "gamma", &value, &found));
|
||
|
|
AKSL_CHECK(found == 0);
|
||
|
|
AKSL_CHECK(value == (void *)0x1); /* untouched when the key is absent */
|
||
|
|
|
||
|
|
/* NULL value out-param is allowed: sometimes you only want to know if it is there. */
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_get(&map, "alpha", NULL, &found));
|
||
|
|
AKSL_CHECK(found == 1);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Putting an existing key replaces the value rather than adding a second entry. */
|
||
|
|
static int test_put_replaces_an_existing_key(void)
|
||
|
|
{
|
||
|
|
aksl_HashEntry slots[CAP];
|
||
|
|
aksl_HashMap map;
|
||
|
|
int a = 1;
|
||
|
|
int b = 2;
|
||
|
|
void *value = NULL;
|
||
|
|
int found = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_init(&map, slots, CAP));
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, "key", &a));
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, "key", &b));
|
||
|
|
AKSL_CHECK(map.count == 1);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_get(&map, "key", &value, &found));
|
||
|
|
AKSL_CHECK(found == 1);
|
||
|
|
AKSL_CHECK(value == &b);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* The map copies keys into its slots, so it owns them and cannot outlive them. */
|
||
|
|
static int test_the_map_owns_its_keys(void)
|
||
|
|
{
|
||
|
|
aksl_HashEntry slots[CAP];
|
||
|
|
aksl_HashMap map;
|
||
|
|
char key[16] = "transient";
|
||
|
|
int a = 1;
|
||
|
|
int found = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_init(&map, slots, CAP));
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, key, &a));
|
||
|
|
|
||
|
|
/* Scribble over the caller's copy of the key. */
|
||
|
|
memset(key, 'Z', sizeof(key) - 1);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_get(&map, "transient", NULL, &found));
|
||
|
|
AKSL_CHECK(found == 1);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Bounds */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Full is an error naming the capacity, not a silent resize. A table that
|
||
|
|
* reallocates is a table whose entry pointers move underneath anything holding
|
||
|
|
* one; this one has a worst case you can state.
|
||
|
|
*/
|
||
|
|
static int test_a_full_map_refuses_rather_than_resizing(void)
|
||
|
|
{
|
||
|
|
aksl_HashEntry slots[4];
|
||
|
|
aksl_HashMap map;
|
||
|
|
char key[16];
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_init(&map, slots, 4));
|
||
|
|
for ( i = 0; i < 4; i++ ) {
|
||
|
|
snprintf(key, sizeof(key), "k%d", i);
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, key, NULL));
|
||
|
|
}
|
||
|
|
AKSL_CHECK(map.count == 4);
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_hashmap_put(&map, "overflow", NULL),
|
||
|
|
AKERR_OUTOFBOUNDS, "does not resize");
|
||
|
|
AKSL_CHECK(map.count == 4);
|
||
|
|
|
||
|
|
/* Everything already in there is still reachable and still correct. */
|
||
|
|
for ( i = 0; i < 4; i++ ) {
|
||
|
|
int found = 0;
|
||
|
|
snprintf(key, sizeof(key), "k%d", i);
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_get(&map, key, NULL, &found));
|
||
|
|
AKSL_CHECK(found == 1);
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* A key too long for a slot is refused rather than truncated. Truncation would
|
||
|
|
* silently collide two different keys that share a prefix, which is the worst
|
||
|
|
* possible failure for a symbol table -- a lookup that returns the wrong thing.
|
||
|
|
*/
|
||
|
|
static int test_an_oversized_key_is_refused(void)
|
||
|
|
{
|
||
|
|
aksl_HashEntry slots[CAP];
|
||
|
|
aksl_HashMap map;
|
||
|
|
char longkey[AKSL_HASHMAP_MAX_KEY + 8];
|
||
|
|
int found = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_init(&map, slots, CAP));
|
||
|
|
memset(longkey, 'k', sizeof(longkey) - 1);
|
||
|
|
longkey[sizeof(longkey) - 1] = '\0';
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_hashmap_put(&map, longkey, NULL),
|
||
|
|
AKERR_OUTOFBOUNDS, "AKSL_HASHMAP_MAX_KEY");
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_get(&map, longkey, NULL, &found), AKERR_OUTOFBOUNDS);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_remove(&map, longkey, NULL), AKERR_OUTOFBOUNDS);
|
||
|
|
|
||
|
|
/* One byte short of the limit is fine: it is a limit, not an off-by-one. */
|
||
|
|
longkey[AKSL_HASHMAP_MAX_KEY - 1] = '\0';
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, longkey, NULL));
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_get(&map, longkey, NULL, &found));
|
||
|
|
AKSL_CHECK(found == 1);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Removal and tombstones */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_remove(void)
|
||
|
|
{
|
||
|
|
aksl_HashEntry slots[CAP];
|
||
|
|
aksl_HashMap map;
|
||
|
|
int a = 1;
|
||
|
|
int found = 99;
|
||
|
|
int removed = 99;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_init(&map, slots, CAP));
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, "alpha", &a));
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_remove(&map, "alpha", &removed));
|
||
|
|
AKSL_CHECK(removed == 1);
|
||
|
|
AKSL_CHECK(map.count == 0);
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_get(&map, "alpha", NULL, &found));
|
||
|
|
AKSL_CHECK(found == 0);
|
||
|
|
|
||
|
|
/* Removing what is not there is success with removed = 0. */
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_remove(&map, "alpha", &removed));
|
||
|
|
AKSL_CHECK(removed == 0);
|
||
|
|
/* The out-param is optional. */
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_remove(&map, "nothing", NULL));
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The tombstone case, which is the one an open-addressed table gets wrong.
|
||
|
|
*
|
||
|
|
* A tiny capacity forces collisions, so several keys share a probe chain.
|
||
|
|
* Deleting one from the middle of that chain must not cut the keys behind it
|
||
|
|
* loose: if the slot were simply emptied, the probe for a later key would stop
|
||
|
|
* there and report the key missing while it was still sitting in the table.
|
||
|
|
*/
|
||
|
|
static int test_removal_does_not_break_a_probe_chain(void)
|
||
|
|
{
|
||
|
|
aksl_HashEntry slots[4];
|
||
|
|
aksl_HashMap map;
|
||
|
|
char key[16];
|
||
|
|
int found = 0;
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_init(&map, slots, 4));
|
||
|
|
for ( i = 0; i < 4; i++ ) {
|
||
|
|
snprintf(key, sizeof(key), "k%d", i);
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, key, NULL));
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Take out each key in turn and confirm every remaining one is still found. */
|
||
|
|
for ( i = 0; i < 4; i++ ) {
|
||
|
|
int j = 0;
|
||
|
|
snprintf(key, sizeof(key), "k%d", i);
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_remove(&map, key, NULL));
|
||
|
|
for ( j = i + 1; j < 4; j++ ) {
|
||
|
|
snprintf(key, sizeof(key), "k%d", j);
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_get(&map, key, NULL, &found));
|
||
|
|
AKSL_CHECK(found == 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
AKSL_CHECK(map.count == 0);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* A tombstone is reusable, so a table churned through repeatedly does not fill up. */
|
||
|
|
static int test_tombstones_are_reused(void)
|
||
|
|
{
|
||
|
|
aksl_HashEntry slots[4];
|
||
|
|
aksl_HashMap map;
|
||
|
|
char key[16];
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_init(&map, slots, 4));
|
||
|
|
/* Far more insert/remove cycles than there are slots. */
|
||
|
|
for ( i = 0; i < 64; i++ ) {
|
||
|
|
snprintf(key, sizeof(key), "cycle%d", i);
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, key, NULL));
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_remove(&map, key, NULL));
|
||
|
|
}
|
||
|
|
AKSL_CHECK(map.count == 0);
|
||
|
|
/* Still usable afterwards. */
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, "final", NULL));
|
||
|
|
AKSL_CHECK(map.count == 1);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Iteration */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_iterate_visits_every_live_entry(void)
|
||
|
|
{
|
||
|
|
aksl_HashEntry slots[CAP];
|
||
|
|
aksl_HashMap map;
|
||
|
|
SeenLog log;
|
||
|
|
char key[16];
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_init(&map, slots, CAP));
|
||
|
|
for ( i = 0; i < 5; i++ ) {
|
||
|
|
snprintf(key, sizeof(key), "k%d", i);
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_put(&map, key, NULL));
|
||
|
|
}
|
||
|
|
/* One removed, so there is a tombstone for the walk to skip. */
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_remove(&map, "k2", NULL));
|
||
|
|
|
||
|
|
memset((void *)&log, 0x00, sizeof(log));
|
||
|
|
log.break_at = -1;
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_iterate(&map, &record_entry, &log));
|
||
|
|
AKSL_CHECK(log.count == 4);
|
||
|
|
|
||
|
|
/* The break stops it, as everywhere else in this library. */
|
||
|
|
memset((void *)&log, 0x00, sizeof(log));
|
||
|
|
log.break_at = 1;
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_iterate(&map, &record_entry, &log));
|
||
|
|
AKSL_CHECK(log.count == 2);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int test_rejects_null_and_uninitialised(void)
|
||
|
|
{
|
||
|
|
aksl_HashEntry slots[CAP];
|
||
|
|
aksl_HashMap map;
|
||
|
|
aksl_HashMap empty;
|
||
|
|
SeenLog log;
|
||
|
|
int found = 0;
|
||
|
|
|
||
|
|
memset((void *)&empty, 0x00, sizeof(empty));
|
||
|
|
memset((void *)&log, 0x00, sizeof(log));
|
||
|
|
AKSL_CHECK_OK(aksl_hashmap_init(&map, slots, CAP));
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_init(NULL, slots, CAP), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_init(&map, NULL, CAP), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_init(&map, slots, 0), AKERR_VALUE);
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_put(NULL, "k", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_put(&map, NULL, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_get(NULL, "k", NULL, &found), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_get(&map, NULL, NULL, &found), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_get(&map, "k", NULL, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_remove(NULL, "k", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_remove(&map, NULL, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_iterate(NULL, &record_entry, &log), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_iterate(&map, NULL, &log), AKERR_NULLPOINTER);
|
||
|
|
|
||
|
|
/* A map that was never initialised is caught rather than dereferenced. */
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_put(&empty, "k", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_get(&empty, "k", NULL, &found), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_remove(&empty, "k", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_hashmap_iterate(&empty, &record_entry, &log), AKERR_NULLPOINTER);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
int failures = 0;
|
||
|
|
|
||
|
|
akerr_init();
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_fnv1a_known_answers);
|
||
|
|
AKSL_RUN(failures, test_fnv1a_high_bit_bytes_are_unsigned);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_put_and_get);
|
||
|
|
AKSL_RUN(failures, test_put_replaces_an_existing_key);
|
||
|
|
AKSL_RUN(failures, test_the_map_owns_its_keys);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_a_full_map_refuses_rather_than_resizing);
|
||
|
|
AKSL_RUN(failures, test_an_oversized_key_is_refused);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_remove);
|
||
|
|
AKSL_RUN(failures, test_removal_does_not_break_a_probe_chain);
|
||
|
|
AKSL_RUN(failures, test_tombstones_are_reused);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_iterate_visits_every_live_entry);
|
||
|
|
AKSL_RUN(failures, test_rejects_null_and_uninitialised);
|
||
|
|
|
||
|
|
AKSL_REPORT(failures);
|
||
|
|
}
|