Registry¶
Registry test source.
/******************************************************************************
* Copyright (c) Intel Corporation - All rights reserved. *
* This file is part of the LIBXS library. *
* *
* For information on the license, see the LICENSE file. *
* Further information: https://github.com/hfp/libxs/ *
* SPDX-License-Identifier: BSD-3-Clause *
******************************************************************************/
#include <libxs/libxs_reg.h>
#include <stdio.h>
#include <string.h>
#define TEST_CHECK(EXPR) do { \
if (!(EXPR)) { \
fprintf(stderr, "FAIL: %s:%i (%s)\n", __FILE__, __LINE__, #EXPR); \
return EXIT_FAILURE; \
} \
} while(0)
/** Padded struct key: must be memset + element-wise init (as documented). */
typedef struct test_struct_key_t {
int x;
char tag;
/* padding expected between tag and y on most ABIs */
double y;
} test_struct_key_t;
static int test_null_args(void)
{ /* NULL and invalid arguments must not crash and must return NULL / failure */
libxs_registry_t* registry;
libxs_registry_info_t info;
const int key = 42;
registry = libxs_registry_create();
TEST_CHECK(NULL != registry);
/* set: NULL key */
TEST_CHECK(NULL == libxs_registry_set(registry, NULL, sizeof(key), "abc", 4, NULL));
/* set: zero key_size */
TEST_CHECK(NULL == libxs_registry_set(registry, &key, 0, "abc", 4, NULL));
/* set: key_size exceeds maximum */
TEST_CHECK(NULL == libxs_registry_set(registry, &key, LIBXS_REGKEY_MAXSIZE + 1, "abc", 4, NULL));
/* set: zero value_size */
TEST_CHECK(NULL == libxs_registry_set(registry, &key, sizeof(key), NULL, 0, NULL));
/* set: NULL registry */
TEST_CHECK(NULL == libxs_registry_set(NULL, &key, sizeof(key), "abc", 4, NULL));
/* get: NULL registry */
TEST_CHECK(NULL == libxs_registry_get(NULL, &key, sizeof(key), NULL));
/* get: NULL key */
TEST_CHECK(NULL == libxs_registry_get(registry, NULL, sizeof(key), NULL));
/* get: zero key_size */
TEST_CHECK(NULL == libxs_registry_get(registry, &key, 0, NULL));
/* get: key_size exceeds maximum */
TEST_CHECK(NULL == libxs_registry_get(registry, &key, LIBXS_REGKEY_MAXSIZE + 1, NULL));
/* free: NULL registry / NULL key (must not crash) */
libxs_registry_remove(NULL, &key, sizeof(key), NULL);
libxs_registry_remove(registry, NULL, sizeof(key), NULL);
libxs_registry_remove(registry, &key, 0, NULL);
/* begin/next: NULL registry */
TEST_CHECK(NULL == libxs_registry_begin(NULL, NULL, NULL));
TEST_CHECK(NULL == libxs_registry_next(NULL, NULL, NULL));
/* info: NULL args */
TEST_CHECK(EXIT_SUCCESS != libxs_registry_info(NULL, &info));
TEST_CHECK(EXIT_SUCCESS != libxs_registry_info(registry, NULL));
libxs_registry_destroy(registry);
/* destroy NULL is safe */
libxs_registry_destroy(NULL);
return EXIT_SUCCESS;
}
static int test_set_get_basic(void)
{ /* register with deferred init, retrieve, re-register same size, auto-realloc larger */
const int key = 1;
const char hello[] = "hello";
const char world[] = "world";
const char toolarge[] = "this is a much larger payload";
char* v;
libxs_registry_t* registry = libxs_registry_create();
TEST_CHECK(NULL != registry);
/* deferred init: register without value, then fill in */
v = (char*)libxs_registry_set(registry, &key, sizeof(key), NULL, sizeof(hello), NULL);
TEST_CHECK(NULL != v);
memcpy(v, hello, sizeof(hello));
/* retrieve: must match the deferred value */
v = (char*)libxs_registry_get(registry, &key, sizeof(key), NULL);
TEST_CHECK(NULL != v);
TEST_CHECK(0 == strcmp(v, hello));
/* re-register with same-size value: overwrites in-place */
v = (char*)libxs_registry_set(registry, &key, sizeof(key), world, sizeof(world), NULL);
TEST_CHECK(NULL != v);
TEST_CHECK(0 == strcmp(v, world));
/* re-register with LARGER value: auto-realloc succeeds */
v = (char*)libxs_registry_set(registry, &key, sizeof(key), toolarge, sizeof(toolarge), NULL);
TEST_CHECK(NULL != v);
TEST_CHECK(0 == strcmp(v, toolarge));
/* retrieve confirms the larger value is stored */
v = (char*)libxs_registry_get(registry, &key, sizeof(key), NULL);
TEST_CHECK(NULL != v);
TEST_CHECK(0 == strcmp(v, toolarge));
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_free_and_reregister(void)
{ /* free removes entry, get returns NULL, re-register with larger value succeeds */
const int key = 7;
const char small[] = "ab";
const char large[] = "abcdef";
char* v;
libxs_registry_t* registry = libxs_registry_create();
TEST_CHECK(NULL != registry);
v = (char*)libxs_registry_set(registry, &key, sizeof(key), small, sizeof(small), NULL);
TEST_CHECK(NULL != v);
libxs_registry_remove(registry, &key, sizeof(key), NULL);
/* get after free must return NULL */
TEST_CHECK(NULL == libxs_registry_get(registry, &key, sizeof(key), NULL));
/* double-free must not crash */
libxs_registry_remove(registry, &key, sizeof(key), NULL);
/* re-register with larger payload succeeds (tombstone reused) */
v = (char*)libxs_registry_set(registry, &key, sizeof(key), large, sizeof(large), NULL);
TEST_CHECK(NULL != v);
TEST_CHECK(0 == strcmp(v, large));
/* retrieve confirms re-registration */
v = (char*)libxs_registry_get(registry, &key, sizeof(key), NULL);
TEST_CHECK(NULL != v);
TEST_CHECK(0 == strcmp(v, large));
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_iteration(void)
{ /* iterate over populated registry and empty registry */
typedef int key_type;
const key_type keys[] = { 10, 20, 30, 40, 50 };
const int n = (int)(sizeof(keys) / sizeof(keys[0]));
int visited[5];
int i, count;
libxs_registry_t* registry = libxs_registry_create();
TEST_CHECK(NULL != registry);
/* empty registry: begin returns NULL */
TEST_CHECK(NULL == libxs_registry_begin(registry, NULL, NULL));
/* populate */
for (i = 0; i < n; ++i) {
int* v = (int*)libxs_registry_set(registry, &keys[i], sizeof(keys[0]),
&keys[i], sizeof(int), NULL);
TEST_CHECK(NULL != v && *v == keys[i]);
}
/* iterate and count, verify each key appears exactly once */
memset(visited, 0, sizeof(visited));
{ const void* regkey = NULL;
size_t cursor = 0;
const void* entry = libxs_registry_begin(registry, ®key, &cursor);
count = 0;
for (; NULL != entry; entry = libxs_registry_next(registry, ®key, &cursor)) {
const key_type k = *(const key_type*)regkey;
int found = 0;
for (i = 0; i < n; ++i) {
if (keys[i] == k) { visited[i]++; found = 1; break; }
}
TEST_CHECK(0 != found);
++count;
}
}
TEST_CHECK(count == n);
for (i = 0; i < n; ++i) TEST_CHECK(1 == visited[i]);
/* begin with NULL key-out pointer also works */
TEST_CHECK(NULL != libxs_registry_begin(registry, NULL, NULL));
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_info(void)
{ /* check info before and after inserts, and after free */
libxs_registry_info_t info;
const int key1 = 1, key2 = 2;
const char val[] = "data";
libxs_registry_t* registry = libxs_registry_create();
TEST_CHECK(NULL != registry);
TEST_CHECK(EXIT_SUCCESS == libxs_registry_info(registry, &info));
TEST_CHECK(0 == info.size);
TEST_CHECK(0 < info.capacity);
TEST_CHECK(LIBXS_ISPOT(info.capacity));
TEST_CHECK(NULL != libxs_registry_set(registry, &key1, sizeof(key1), val, sizeof(val), NULL));
TEST_CHECK(NULL != libxs_registry_set(registry, &key2, sizeof(key2), val, sizeof(val), NULL));
TEST_CHECK(EXIT_SUCCESS == libxs_registry_info(registry, &info));
TEST_CHECK(2 == info.size);
TEST_CHECK(0 < info.nbytes);
libxs_registry_remove(registry, &key1, sizeof(key1), NULL);
TEST_CHECK(EXIT_SUCCESS == libxs_registry_info(registry, &info));
TEST_CHECK(1 == info.size);
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_growth(void)
{ /* insert enough entries to trigger at least one table growth */
libxs_registry_info_t info;
const int count = LIBXS_REGISTRY_NBUCKETS * 2; /* well beyond 75% load */
int i;
libxs_registry_t* registry = libxs_registry_create();
TEST_CHECK(NULL != registry);
TEST_CHECK(EXIT_SUCCESS == libxs_registry_info(registry, &info));
{ const size_t initial_cap = info.capacity;
for (i = 0; i < count; ++i) {
int* v = (int*)libxs_registry_set(registry, &i, sizeof(i), &i, sizeof(int), NULL);
TEST_CHECK(NULL != v && *v == i);
}
TEST_CHECK(EXIT_SUCCESS == libxs_registry_info(registry, &info));
TEST_CHECK(info.size == (size_t)count);
TEST_CHECK(info.capacity > initial_cap); /* must have grown */
TEST_CHECK(LIBXS_ISPOT(info.capacity));
/* verify all entries survive the growth/rehash */
for (i = 0; i < count; ++i) {
const int* v = (const int*)libxs_registry_get(registry, &i, sizeof(i), NULL);
TEST_CHECK(NULL != v && *v == i);
}
}
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_struct_key(void)
{ /* padded struct key: must memset then element-wise init (documented requirement) */
test_struct_key_t k1, k2;
double val = 3.14;
double* v;
libxs_registry_t* registry = libxs_registry_create();
TEST_CHECK(NULL != registry);
/* correct initialization: memset + element-wise */
memset(&k1, 0, sizeof(k1));
k1.x = 42; k1.tag = 'A'; k1.y = 1.0;
v = (double*)libxs_registry_set(registry, &k1, sizeof(k1), &val, sizeof(val), NULL);
TEST_CHECK(NULL != v && *v == val);
/* same logical key, same binary init */
memset(&k2, 0, sizeof(k2));
k2.x = 42; k2.tag = 'A'; k2.y = 1.0;
v = (double*)libxs_registry_get(registry, &k2, sizeof(k2), NULL);
TEST_CHECK(NULL != v && *v == val);
/* different key */
memset(&k2, 0, sizeof(k2));
k2.x = 42; k2.tag = 'B'; k2.y = 1.0;
TEST_CHECK(NULL == libxs_registry_get(registry, &k2, sizeof(k2), NULL));
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_tls_cache(void)
{ /* repeated get should be served from TLS cache; free invalidates cache */
const int key = 99;
const char val[] = "cached";
char* v;
int i;
libxs_registry_t* registry = libxs_registry_create();
TEST_CHECK(NULL != registry);
v = (char*)libxs_registry_set(registry, &key, sizeof(key), val, sizeof(val), NULL);
TEST_CHECK(NULL != v);
{ /* first get populates TLS cache, second get hits it (both must return same pointer) */
const char* v1 = (const char*)libxs_registry_get(registry, &key, sizeof(key), NULL);
const char* v2 = (const char*)libxs_registry_get(registry, &key, sizeof(key), NULL);
TEST_CHECK(NULL != v1 && NULL != v2);
TEST_CHECK(v1 == v2); /* same pointer */
TEST_CHECK(0 == strcmp(v1, val));
}
/* many repeated gets must all succeed (hammer cache path) */
for (i = 0; i < 1000; ++i) {
TEST_CHECK(NULL != libxs_registry_get(registry, &key, sizeof(key), NULL));
}
/* free invalidates cache; subsequent get must return NULL */
libxs_registry_remove(registry, &key, sizeof(key), NULL);
TEST_CHECK(NULL == libxs_registry_get(registry, &key, sizeof(key), NULL));
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_multiple_registries(void)
{ /* two independent registries with same keys must not interfere */
const int key = 1;
const int v1 = 100, v2 = 200;
int* p;
libxs_registry_t *r1 = libxs_registry_create();
libxs_registry_t *r2 = libxs_registry_create();
TEST_CHECK(NULL != r1 && NULL != r2);
p = (int*)libxs_registry_set(r1, &key, sizeof(key), &v1, sizeof(int), NULL);
TEST_CHECK(NULL != p && *p == v1);
p = (int*)libxs_registry_set(r2, &key, sizeof(key), &v2, sizeof(int), NULL);
TEST_CHECK(NULL != p && *p == v2);
/* get from each registry returns its own value */
p = (int*)libxs_registry_get(r1, &key, sizeof(key), NULL);
TEST_CHECK(NULL != p && *p == v1);
p = (int*)libxs_registry_get(r2, &key, sizeof(key), NULL);
TEST_CHECK(NULL != p && *p == v2);
/* destroy one, other is unaffected */
libxs_registry_destroy(r1);
p = (int*)libxs_registry_get(r2, &key, sizeof(key), NULL);
TEST_CHECK(NULL != p && *p == v2);
libxs_registry_destroy(r2);
return EXIT_SUCCESS;
}
static int test_has(void)
{ /* _has returns non-zero for existing keys, zero for missing */
const int key = 42, missing = 99;
const double val = 3.14;
libxs_registry_t* registry = libxs_registry_create();
TEST_CHECK(NULL != registry);
TEST_CHECK(0 == libxs_registry_has(registry, &key, sizeof(key), NULL));
TEST_CHECK(NULL != libxs_registry_set(registry, &key, sizeof(key), &val, sizeof(val), NULL));
TEST_CHECK(0 != libxs_registry_has(registry, &key, sizeof(key), NULL));
TEST_CHECK(0 == libxs_registry_has(registry, &missing, sizeof(missing), NULL));
/* NULL / invalid args */
TEST_CHECK(0 == libxs_registry_has(NULL, &key, sizeof(key), NULL));
TEST_CHECK(0 == libxs_registry_has(registry, NULL, sizeof(key), NULL));
TEST_CHECK(0 == libxs_registry_has(registry, &key, 0, NULL));
/* remove → no longer found */
libxs_registry_remove(registry, &key, sizeof(key), NULL);
TEST_CHECK(0 == libxs_registry_has(registry, &key, sizeof(key), NULL));
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_value_size(void)
{ /* _value_size returns stored size, 0 for missing keys */
const int key = 7;
const char small[] = "ab";
const char large[] = "abcdef";
libxs_registry_t* registry = libxs_registry_create();
TEST_CHECK(NULL != registry);
TEST_CHECK(0 == libxs_registry_value_size(registry, &key, sizeof(key), NULL));
TEST_CHECK(NULL != libxs_registry_set(registry, &key, sizeof(key), small, sizeof(small), NULL));
TEST_CHECK(sizeof(small) == libxs_registry_value_size(registry, &key, sizeof(key), NULL));
/* auto-realloc to larger → value_size grows */
TEST_CHECK(NULL != libxs_registry_set(registry, &key, sizeof(key), large, sizeof(large), NULL));
TEST_CHECK(sizeof(large) == libxs_registry_value_size(registry, &key, sizeof(key), NULL));
/* NULL / invalid args */
TEST_CHECK(0 == libxs_registry_value_size(NULL, &key, sizeof(key), NULL));
TEST_CHECK(0 == libxs_registry_value_size(registry, NULL, sizeof(key), NULL));
TEST_CHECK(0 == libxs_registry_value_size(registry, &key, 0, NULL));
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_save_load(void)
{ /* save registry to buffer, load from buffer, verify all entries survive */
const int keys[] = { 1, 2, 3, 4, 5 };
const char* vals[] = { "alpha", "beta", "gamma", "delta", "epsilon" };
const int n = (int)(sizeof(keys) / sizeof(keys[0]));
int i;
size_t buf_size = 0;
void* buf;
libxs_registry_t* registry = libxs_registry_create();
libxs_registry_t* loaded;
TEST_CHECK(NULL != registry);
for (i = 0; i < n; ++i) {
TEST_CHECK(NULL != libxs_registry_set(registry, &keys[i], sizeof(keys[0]),
vals[i], strlen(vals[i]) + 1, NULL));
}
/* query required size */
TEST_CHECK(EXIT_SUCCESS == libxs_registry_save(registry, NULL, &buf_size));
TEST_CHECK(0 < buf_size);
buf = malloc(buf_size);
TEST_CHECK(NULL != buf);
TEST_CHECK(EXIT_SUCCESS == libxs_registry_save(registry, buf, &buf_size));
/* load from buffer */
loaded = libxs_registry_load(buf, buf_size, NULL, NULL);
TEST_CHECK(NULL != loaded);
/* verify all entries */
{ libxs_registry_info_t info;
TEST_CHECK(EXIT_SUCCESS == libxs_registry_info(loaded, &info));
TEST_CHECK(info.size == (size_t)n);
}
for (i = 0; i < n; ++i) {
const char* v = (const char*)libxs_registry_get(loaded, &keys[i], sizeof(keys[0]), NULL);
TEST_CHECK(NULL != v);
TEST_CHECK(0 == strcmp(v, vals[i]));
}
/* overwrite a loaded entry (transitions from ext to owned) */
{ const char* newval = "replaced value that is longer";
char* v = (char*)libxs_registry_set(loaded, &keys[0], sizeof(keys[0]),
newval, strlen(newval) + 1, NULL);
TEST_CHECK(NULL != v);
TEST_CHECK(0 == strcmp(v, newval));
}
/* remove a loaded entry (ext pointer must not be freed) */
libxs_registry_remove(loaded, &keys[1], sizeof(keys[0]), NULL);
TEST_CHECK(NULL == libxs_registry_get(loaded, &keys[1], sizeof(keys[0]), NULL));
libxs_registry_destroy(loaded);
free(buf);
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_save_load_inline(void)
{ /* values small enough for inline storage round-trip correctly */
const int key = 42;
const int val = 12345;
size_t buf_size = 0;
void* buf;
libxs_registry_t* registry = libxs_registry_create();
libxs_registry_t* loaded;
TEST_CHECK(NULL != registry);
TEST_CHECK(NULL != libxs_registry_set(registry, &key, sizeof(key), &val, sizeof(val), NULL));
TEST_CHECK(EXIT_SUCCESS == libxs_registry_save(registry, NULL, &buf_size));
buf = malloc(buf_size);
TEST_CHECK(NULL != buf);
TEST_CHECK(EXIT_SUCCESS == libxs_registry_save(registry, buf, &buf_size));
loaded = libxs_registry_load(buf, buf_size, NULL, NULL);
TEST_CHECK(NULL != loaded);
{ const int* v = (const int*)libxs_registry_get(loaded, &key, sizeof(key), NULL);
TEST_CHECK(NULL != v && *v == val);
}
libxs_registry_destroy(loaded);
free(buf);
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_save_load_empty(void)
{ /* save/load an empty registry */
size_t buf_size = 0;
void* buf;
libxs_registry_t* registry = libxs_registry_create();
libxs_registry_t* loaded;
libxs_registry_info_t info;
TEST_CHECK(NULL != registry);
TEST_CHECK(EXIT_SUCCESS == libxs_registry_save(registry, NULL, &buf_size));
buf = malloc(buf_size);
TEST_CHECK(NULL != buf);
TEST_CHECK(EXIT_SUCCESS == libxs_registry_save(registry, buf, &buf_size));
loaded = libxs_registry_load(buf, buf_size, NULL, NULL);
TEST_CHECK(NULL != loaded);
TEST_CHECK(EXIT_SUCCESS == libxs_registry_info(loaded, &info));
TEST_CHECK(0 == info.size);
libxs_registry_destroy(loaded);
free(buf);
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
static int test_load_invalid(void)
{ /* invalid buffers must return NULL */
const char garbage[] = "not a registry";
TEST_CHECK(NULL == libxs_registry_load(NULL, 100, NULL, NULL));
TEST_CHECK(NULL == libxs_registry_load(garbage, sizeof(garbage), NULL, NULL));
TEST_CHECK(NULL == libxs_registry_load(garbage, 0, NULL, NULL));
return EXIT_SUCCESS;
}
typedef struct test_fixup_entry_t {
int data;
void (*callback)(int);
} test_fixup_entry_t;
static int test_fixup_counter;
static void test_fixup_cb(int v)
{
test_fixup_counter += v;
}
static void test_fixup_fn(void* value, const void* key, size_t key_size,
size_t value_size, void* udata)
{
test_fixup_entry_t* e = (test_fixup_entry_t*)value;
LIBXS_UNUSED(key); LIBXS_UNUSED(key_size);
LIBXS_UNUSED(value_size); LIBXS_UNUSED(udata);
e->callback = test_fixup_cb;
}
static int test_save_load_fixup(void)
{ /* fixup callback restores function pointers after load */
const int key = 1;
test_fixup_entry_t entry;
size_t buf_size = 0;
void* buf;
libxs_registry_t* registry = libxs_registry_create();
libxs_registry_t* loaded;
TEST_CHECK(NULL != registry);
memset(&entry, 0, sizeof(entry));
entry.data = 42;
entry.callback = test_fixup_cb;
TEST_CHECK(NULL != libxs_registry_set(registry, &key, sizeof(key),
&entry, sizeof(entry), NULL));
TEST_CHECK(EXIT_SUCCESS == libxs_registry_save(registry, NULL, &buf_size));
buf = malloc(buf_size);
TEST_CHECK(NULL != buf);
TEST_CHECK(EXIT_SUCCESS == libxs_registry_save(registry, buf, &buf_size));
loaded = libxs_registry_load(buf, buf_size, test_fixup_fn, NULL);
TEST_CHECK(NULL != loaded);
{ const test_fixup_entry_t* v = (const test_fixup_entry_t*)libxs_registry_get(
loaded, &key, sizeof(key), NULL);
TEST_CHECK(NULL != v);
TEST_CHECK(42 == v->data);
TEST_CHECK(test_fixup_cb == v->callback);
test_fixup_counter = 0;
v->callback(10);
TEST_CHECK(10 == test_fixup_counter);
}
libxs_registry_destroy(loaded);
free(buf);
libxs_registry_destroy(registry);
return EXIT_SUCCESS;
}
int main(int argc, char* argv[])
{
int result = EXIT_SUCCESS;
LIBXS_UNUSED(argc); LIBXS_UNUSED(argv);
if (EXIT_SUCCESS == result) result = test_null_args();
if (EXIT_SUCCESS == result) result = test_set_get_basic();
if (EXIT_SUCCESS == result) result = test_free_and_reregister();
if (EXIT_SUCCESS == result) result = test_iteration();
if (EXIT_SUCCESS == result) result = test_info();
if (EXIT_SUCCESS == result) result = test_growth();
if (EXIT_SUCCESS == result) result = test_struct_key();
if (EXIT_SUCCESS == result) result = test_tls_cache();
if (EXIT_SUCCESS == result) result = test_multiple_registries();
if (EXIT_SUCCESS == result) result = test_has();
if (EXIT_SUCCESS == result) result = test_value_size();
if (EXIT_SUCCESS == result) result = test_save_load();
if (EXIT_SUCCESS == result) result = test_save_load_inline();
if (EXIT_SUCCESS == result) result = test_save_load_empty();
if (EXIT_SUCCESS == result) result = test_load_invalid();
if (EXIT_SUCCESS == result) result = test_save_load_fixup();
return result;
}