]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Tests for slab allocator
authorNick Porter <nick@portercomputing.co.uk>
Wed, 21 Dec 2022 19:25:07 +0000 (19:25 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 12 Jan 2023 15:10:14 +0000 (09:10 -0600)
src/lib/util/all.mk
src/lib/util/slab_tests.c [new file with mode: 0644]
src/lib/util/slab_tests.mk [new file with mode: 0644]

index fa20525ed1ec75a64e186235cf895802f9565acb..855353a0a3b2d2f0b9cabad0d8152a3228d48fbf 100644 (file)
@@ -16,6 +16,7 @@ SUBMAKEFILES := \
        rb_tests.mk \
        sbuff_tests.mk \
        size_tests.mk \
+       slab_tests.mk \
        strerror_tests.mk \
        time_tests.mk
 
diff --git a/src/lib/util/slab_tests.c b/src/lib/util/slab_tests.c
new file mode 100644 (file)
index 0000000..54fc211
--- /dev/null
@@ -0,0 +1,689 @@
+/** Tests for slab allocator
+ *
+ * @file src/lib/util/slab_tests.c
+ *
+ * @copyright 2023 Network RADIUS SARL <legal@networkradius.com>
+ */
+#include <freeradius-devel/util/acutest.h>
+#include <freeradius-devel/util/acutest_helpers.h>
+
+#include "slab.h"
+
+typedef struct {
+       int     num;
+       char    *name;
+} test_element_t;
+
+typedef struct {
+       int     count;
+} test_uctx_t;
+
+typedef struct {
+       int     initial;
+} test_conf_t;
+
+static int test_element_free(test_element_t *elem, void *uctx)
+{
+       test_uctx_t     *test_uctx = uctx;
+       test_uctx->count = strlen(elem->name);
+       return 0;
+}
+
+static fr_time_t test_time_base = fr_time_wrap(1);
+static fr_time_t test_time(void)
+{
+       return test_time_base;
+}
+
+FR_SLAB_TYPES(test, test_element_t);
+
+FR_SLAB_FUNCS(test, test_element_t, fr_time_delta_from_sec(1));
+
+/** Test basic allocation and reservation of elements
+ *
+ */
+static void test_alloc(void)
+{
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[5];
+       test_uctx_t             test_uctx, test_uctx2;
+       int                     ret = -1;
+
+       /*
+        *      Each slab will contain 2 elements, maximum of 4 elements allocated from slabs.
+        */
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, NULL, 2, 1, 4, false, 0, 0, NULL, NULL, true, false);
+       TEST_CHECK(ret == 0);
+       TEST_CHECK(test_slab_list != NULL);
+
+       test_elements[0] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[0] != NULL);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 1);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 1);
+
+       test_uctx.count = 0;
+       test_uctx2.count = 0;
+
+       /* "if" to keep clang scan happy */
+       if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "Hello there");
+       if (test_elements[0]) fr_test_element_t_slab_set_destructor(test_elements[0], test_element_free, &test_uctx);
+
+       test_elements[1] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[1] != NULL);
+       TEST_CHECK(test_elements[1] != test_elements[0]);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 1);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 2);
+
+       /* This will cause a second slab to be allocated */
+       test_elements[2] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[2] != NULL);
+       if (test_elements[2]) test_elements[2]->name = talloc_strdup(test_elements[2], "Hello there testing");
+       if (test_elements[2]) fr_test_element_t_slab_set_destructor(test_elements[2], test_element_free, &test_uctx2);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 3);
+
+       test_elements[3] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[3] != NULL);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 4);
+
+       /* This is more elements than max_elements */
+       test_elements[4] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[4] != NULL);
+       /* Allocations beyond the maximum do not amend the slab stats */
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 4);
+
+       if (test_elements[0]) fr_test_slab_release(test_elements[0]);
+       TEST_CHECK(test_uctx.count == 11);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 3);
+
+       if (test_elements[1]) fr_test_slab_release(test_elements[1]);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 2);
+
+       if (test_elements[2]) fr_test_slab_release(test_elements[2]);
+       TEST_CHECK(test_uctx2.count == 19);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 1);
+
+       talloc_free(test_slab_list);
+}
+
+/** Test allocation beyond max fails correctly
+ *
+ */
+static void test_alloc_fail(void)
+{
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[5];
+       int                     ret = -1;
+
+       /*
+        *      Each slab will contain 2 elements, maximum of 4 elements allocated from slabs.
+        */
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, NULL, 2, 1, 4, true, 0, 0, NULL, NULL, true, false);
+       TEST_CHECK(ret == 0);
+       TEST_CHECK(test_slab_list != NULL);
+
+       test_elements[0] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[0] != NULL);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 1);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 1);
+
+       test_elements[1] = fr_test_slab_reserve(test_slab_list);
+       test_elements[2] = fr_test_slab_reserve(test_slab_list);
+       test_elements[3] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[3] != NULL);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 4);
+
+       /* This is more elements than max_elements */
+       test_elements[4] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[4] == NULL);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 4);
+
+       talloc_free(test_slab_list);
+}
+
+/** Test that freeing an element makes it available for reuse with the element reset between uses
+ *
+ */
+static void test_reuse_reset(void)
+{
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[5];
+       test_uctx_t             test_uctx;
+       int                     ret = -1;
+
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, NULL, 2, 1, 4, false, 0, 0, NULL, NULL, true, false);
+       TEST_CHECK(ret == 0);
+       TEST_CHECK(test_slab_list != NULL);
+
+       test_elements[0] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[0] != NULL);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 1);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 1);
+
+       test_uctx.count = 0;
+
+       if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "Hello there");
+       if (test_elements[0]) fr_test_element_t_slab_set_destructor(test_elements[0], test_element_free, &test_uctx);
+
+       test_elements[1] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[1] != NULL);
+       TEST_CHECK(test_elements[1] != test_elements[0]);
+
+       test_elements[2] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[2] != NULL);
+
+       test_elements[3] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[3] != NULL);
+
+       if (test_elements[0]) fr_test_slab_release(test_elements[0]);
+       TEST_CHECK(test_uctx.count == 11);
+
+       /*
+        *      Having released the first element allocated from a slab
+        *      reserving another should grab that first one again, but
+        *      with the entry memset to zero.
+        */
+       test_elements[4] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[4] != NULL);
+       TEST_CHECK(test_elements[4] == test_elements[0]);
+       if (test_elements[4]) TEST_CHECK(test_elements[4]->name == NULL);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 4);
+
+       /*
+        *      Releasing the first element should reset the destructor
+        *      so releasing this re-use of it will not update the result
+        *      of the initial release.
+        */
+       if (test_elements[4]) test_elements[4]->name = talloc_strdup(test_elements[4], "Different length string");
+       if (test_elements[4]) fr_test_slab_release(test_elements[4]);
+       TEST_CHECK(test_uctx.count == 11);
+
+       talloc_free(test_slab_list);
+}
+
+/** Test that freeing an element makes it available for reuse with the element not reset between uses
+ *
+ */
+static void test_reuse_noreset(void)
+{
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[3];
+       test_uctx_t             test_uctx;
+       int                     ret = -1;
+
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, NULL, 2, 1, 4, false, 0, 0, NULL, NULL, false, false);
+       TEST_CHECK(ret == 0);
+       TEST_CHECK(test_slab_list != NULL);
+
+       test_elements[0] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[0] != NULL);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 1);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 1);
+
+       test_uctx.count = 0;
+
+       if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "Hello there");
+       if (test_elements[0]) fr_test_element_t_slab_set_destructor(test_elements[0], test_element_free, &test_uctx);
+
+       test_elements[1] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[1] != NULL);
+       TEST_CHECK(test_elements[1] != test_elements[0]);
+
+       if (test_elements[0]) fr_test_slab_release(test_elements[0]);
+       TEST_CHECK(test_uctx.count == 11);
+
+       /*
+        *      Having released the first element allocated from a slab
+        *      reserving another should grab that first one again.
+        *      Since no reset was done, the element should be as it was before.
+        */
+       test_elements[2] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[2] != NULL);
+       TEST_CHECK(test_elements[2] == test_elements[0]);
+       if (test_elements[0] && test_elements[2]) TEST_CHECK(test_elements[2]->name == test_elements[0]->name);
+
+       /*
+        *      Replace the element's string so that the callback on relase has
+        *      a different string to work on.
+        */
+       if (test_elements[2]) talloc_free(test_elements[2]->name);
+       if (test_elements[2]) test_elements[2]->name = talloc_strdup(test_elements[2], "Different length string");
+       if (test_elements[2]) fr_test_slab_release(test_elements[2]);
+       TEST_CHECK(test_uctx.count == 23);
+
+       talloc_free(test_slab_list);
+}
+
+/** Test that talloc freeing an element results in destructor being called
+ *
+ */
+static void test_free(void)
+{
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_element;
+       test_uctx_t             test_uctx;
+       int                     ret = -1;
+
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, NULL, 2, 1, 4, false, 0, 0, NULL, NULL, true, false);
+       TEST_CHECK(ret == 0);
+       TEST_CHECK(test_slab_list != NULL);
+
+       test_element = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_element != NULL);
+
+       test_uctx.count = 0;
+
+       if (test_element) test_element->name = talloc_strdup(test_element, "Hello there");
+       if (test_element) fr_test_element_t_slab_set_destructor(test_element, test_element_free, &test_uctx);
+
+       if (test_element) talloc_free(test_element);
+       TEST_CHECK(test_uctx.count == 11);
+
+       talloc_free(test_slab_list);
+}
+
+static int test_element_alloc(test_element_t *elem, void *uctx)
+{
+       test_conf_t     *test_conf = uctx;
+       elem->num = test_conf->initial;
+       return 0;
+}
+
+/** Test that a callback correctly initialises slab elements on first use
+ *
+ */
+static void test_init_1(void)
+{
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[2];
+       test_conf_t             test_conf = { .initial = 10 };
+       int                     ret = -1;
+
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, NULL, 1, 1, 4, false, 0, 0, test_element_alloc, &test_conf, false, false);
+       TEST_CHECK(ret == 0);
+       TEST_CHECK(test_slab_list != NULL);
+
+       test_elements[0] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[0] != NULL);
+       TEST_CHECK(test_elements[0] && (test_elements[0]->num == 10));
+
+       /*
+        *      Change element data and release
+        */
+       if (test_elements[0]) {
+               test_elements[0]->num = 5;
+               fr_test_slab_release(test_elements[0]);
+       }
+
+       /*
+        *      Slab set not to reset elements, nor re-init
+        *      Re-reserve and check element is unchanged.
+        */
+       test_elements[1] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[1] != NULL);
+       TEST_CHECK(test_elements[1] == test_elements[0]);
+       if (test_elements[1]) TEST_CHECK(test_elements[1]->num == 5);
+
+       talloc_free(test_slab_list);
+}
+
+/** Test that a callback correctly re-initialises slab elements on use
+ *
+ */
+static void test_init_2(void)
+{
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[2];
+       test_conf_t             test_conf = { .initial = 10 };
+       int                     ret = -1;
+
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, NULL, 1, 1, 4, false, 0, 0, test_element_alloc, &test_conf, false, true);
+       TEST_CHECK(ret == 0);
+       TEST_CHECK(test_slab_list != NULL);
+
+       test_elements[0] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[0] != NULL);
+       TEST_CHECK(test_elements[0] && (test_elements[0]->num == 10));
+
+       /*
+        *      Change element data and release
+        */
+       if (test_elements[0]) {
+               test_elements[0]->num = 5;
+               fr_test_slab_release(test_elements[0]);
+       }
+
+       /*
+        *      Slab set re-init
+        *      Re-reserve and check element is reinitialised.
+        */
+       test_elements[1] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[1] != NULL);
+       TEST_CHECK(test_elements[1] == test_elements[0]);
+       if (test_elements[1]) TEST_CHECK(test_elements[1]->num == 10);
+
+       talloc_free(test_slab_list);
+}
+
+/** Test of clearing unused slabs
+ *
+ */
+static void test_clearup_1(void)
+{
+       TALLOC_CTX              *ctx = talloc_init_const("test");
+       fr_event_list_t         *el;
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[6];
+       int                     i, events, ret = -1;
+
+       el = fr_event_list_alloc(ctx, NULL, NULL);
+       fr_event_list_set_time_func(el, test_time);
+
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, el, 2, 1, 6, false, 0, 0, NULL, NULL, true, false);
+       TEST_CHECK(ret == 0);
+
+       /*
+        *      Allocate all the slab elements
+        */
+       for (i = 0; i < 6; i++) {
+               test_elements[i] = fr_test_slab_reserve(test_slab_list);
+               TEST_CHECK(test_elements[i] != NULL);
+       }
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 3);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 6);
+
+       /*
+        *      Release four of the six elements
+        */
+       for (i = 0; i < 4; i++) {
+               fr_test_slab_release(test_elements[i]);
+       }
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 3);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 2);
+
+       /*
+        *      Running clearup should free one slab - half of the
+        *      difference between the high water mark and the in use count.
+        */
+       test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(2));
+       events = fr_event_corral(el, test_time_base, true);
+       TEST_CHECK(events == 1);
+       fr_event_service(el);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 2);
+
+       talloc_free(test_slab_list);
+       talloc_free(ctx);
+}
+
+/** Test that slab clearing does not go beyond the minimum
+ *
+ */
+static void test_clearup_2(void)
+{
+       TALLOC_CTX              *ctx = talloc_init_const("test");
+       fr_event_list_t         *el;
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[20];
+       int                     i, events, ret = -1;
+
+       el = fr_event_list_alloc(ctx, NULL, NULL);
+       fr_event_list_set_time_func(el, test_time);
+
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, el, 2, 16, 20, false, 0, 0, NULL, NULL, true, false);
+       TEST_CHECK(ret == 0);
+
+       /*
+        *      Allocate all the slab elements
+        */
+       for (i = 0; i < 20; i++) {
+               test_elements[i] = fr_test_slab_reserve(test_slab_list);
+               TEST_CHECK(test_elements[i] != NULL);
+       }
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 10);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 20);
+
+       /*
+        *      Release all of the elements
+        */
+       for (i = 0; i < 20; i++) {
+               fr_test_slab_release(test_elements[i]);
+       }
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 10);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       /*
+        *      Running clearup should free two slabs - the minimum element
+        *      count will keep the remainder allocated
+        */
+       test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(2));
+       events = fr_event_corral(el, test_time_base, true);
+       TEST_CHECK(events == 1);
+       fr_event_service(el);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 8);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       /*
+        *      Re-run the event - no more slabs should be cleared
+        */
+       test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(2));
+       events = fr_event_corral(el, test_time_base, true);
+       TEST_CHECK(events == 1);
+       fr_event_service(el);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 8);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       talloc_free(test_slab_list);
+       talloc_free(ctx);
+}
+
+/** Test that repeated clearing frees more slabs
+ *
+ */
+static void test_clearup_3(void)
+{
+       TALLOC_CTX              *ctx = talloc_init_const("test");
+       fr_event_list_t         *el;
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[20];
+       int                     i, events, ret = -1;
+
+       el = fr_event_list_alloc(ctx, NULL, NULL);
+       fr_event_list_set_time_func(el, test_time);
+
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, el, 2, 0, 20, false, 0, 0, NULL, NULL, true, false);
+       TEST_CHECK(ret == 0);
+
+       /*
+        *      Allocate all the slab elements
+        */
+       for (i = 0; i < 20; i++) {
+               test_elements[i] = fr_test_slab_reserve(test_slab_list);
+               TEST_CHECK(test_elements[i] != NULL);
+       }
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 10);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 20);
+
+       /*
+        *      Release all of the elements
+        */
+       for (i = 0; i < 20; i++) {
+               fr_test_slab_release(test_elements[i]);
+       }
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 10);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       /*
+        *      Running clearup should free five slabs (20 - 0) / 2 / 2 = 5
+        */
+       test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(2));
+       events = fr_event_corral(el, test_time_base, true);
+       TEST_CHECK(events == 1);
+       fr_event_service(el);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 5);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       /*
+        *      Re-run the event - two more slabs should be freed (10 - 0) / 2 / 2 = 2.5
+        */
+       test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(2));
+       events = fr_event_corral(el, test_time_base, true);
+       TEST_CHECK(events == 1);
+       fr_event_service(el);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 3);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       /*
+        *      Re-run the event - one more slab should be freed (6 - 0) / 2 / 2 = 1.5
+        */
+       test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(2));
+       events = fr_event_corral(el, test_time_base, true);
+       TEST_CHECK(events == 1);
+       fr_event_service(el);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 2);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       /*
+        *      Re-run the event - one more slab should be freed (4 - 0) / 2 / 2 = 1
+        */
+       test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(2));
+       events = fr_event_corral(el, test_time_base, true);
+       TEST_CHECK(events == 1);
+       fr_event_service(el);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 1);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       /*
+        *      Re-run the event - no more will be freed as (2 - 0) / 2 / 2 = 0.5
+        */
+       test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(2));
+       events = fr_event_corral(el, test_time_base, true);
+       TEST_CHECK(events == 1);
+       fr_event_service(el);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 1);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       talloc_free(test_slab_list);
+       talloc_free(ctx);
+}
+
+/** Test that reserving after clearup results in new slab allocation
+ *
+ */
+static void test_realloc(void)
+{
+       TALLOC_CTX              *ctx = talloc_init_const("test");
+       fr_event_list_t         *el;
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[20];
+       int                     i, events, ret = -1;
+
+       el = fr_event_list_alloc(ctx, NULL, NULL);
+       fr_event_list_set_time_func(el, test_time);
+
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, el, 2, 0, 20, false, 0, 0, NULL, NULL, true, false);
+       TEST_CHECK(ret == 0);
+
+       /*
+        *      Allocate all the slab elements
+        */
+       for (i = 0; i < 20; i++) {
+               test_elements[i] = fr_test_slab_reserve(test_slab_list);
+               TEST_CHECK(test_elements[i] != NULL);
+       }
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 10);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 20);
+
+       /*
+        *      Release all of the elements
+        */
+       for (i = 0; i < 20; i++) {
+               fr_test_slab_release(test_elements[i]);
+       }
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 10);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       /*
+        *      Running clearup should free five slabs
+        */
+       test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(2));
+       events = fr_event_corral(el, test_time_base, true);
+       TEST_CHECK(events == 1);
+       fr_event_service(el);
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 5);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 0);
+
+       /*
+        *      Allocate all the slab elements
+        *      With new slabs allocated, the slab stats will change
+        */
+       for (i = 0; i < 20; i++) {
+               test_elements[i] = fr_test_slab_reserve(test_slab_list);
+               TEST_CHECK(test_elements[i] != NULL);
+       }
+       TEST_CHECK_RET(fr_test_slab_num_allocated(test_slab_list), 10);
+       TEST_CHECK_RET(fr_test_slab_num_elements_used(test_slab_list), 20);
+
+       talloc_free(test_slab_list);
+       talloc_free(ctx);
+}
+
+static void test_child_alloc(void)
+{
+       fr_test_slab_list_t     *test_slab_list;
+       test_element_t          *test_elements[2];
+       int                     ret = -1;
+
+       ret = fr_test_slab_list_alloc(NULL, &test_slab_list, NULL, 2, 1, 2, true, 1, 128, NULL, NULL, false, false);
+       TEST_CHECK(ret == 0);
+       TEST_CHECK(test_slab_list != NULL);
+
+       test_elements[0] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[0] != NULL);
+       test_elements[1] = fr_test_slab_reserve(test_slab_list);
+       TEST_CHECK(test_elements[1] != NULL);
+
+       /*
+        *      Allocate a child of the first element.  If this has used the pool memory
+        *      allocated to the element, then it's location should be after the element
+        *      but before the next element.
+        *      This is a rough test, which can be improved if additional talloc functions
+        *      become available to check pool allocation status.
+        */
+       if (test_elements[0]) {
+               test_elements[0]->name = talloc_strdup(test_elements[0], "Hello there");
+               TEST_CHECK((void *)test_elements[0]->name > (void *)test_elements[0]);
+               if (test_elements[1] && (test_elements[1] > test_elements[0])) {
+                       TEST_CHECK((void *)test_elements[0]->name < (void *)test_elements[1]);
+                       TEST_MSG("element 0: %p, name %p, element 1: %p",
+                                test_elements[0], test_elements[0]->name, test_elements[1]);
+               }
+       }
+
+       talloc_free(test_slab_list);
+}
+
+TEST_LIST = {
+       { "test_alloc",         test_alloc },
+       { "test_alloc_fail",    test_alloc_fail },
+       { "test_reuse_reset",   test_reuse_reset },
+       { "test_reuse_noreset", test_reuse_noreset },
+       { "test_free",          test_free },
+       { "test_init_1",        test_init_1 },
+       { "test_init_2",        test_init_2 },
+       { "test_clearup_1",     test_clearup_1 },
+       { "test_clearup_2",     test_clearup_2 },
+       { "test_clearup_3",     test_clearup_3 },
+       { "test_realloc",       test_realloc },
+       { "test_child_alloc",   test_child_alloc },
+
+       { NULL }
+};
diff --git a/src/lib/util/slab_tests.mk b/src/lib/util/slab_tests.mk
new file mode 100644 (file)
index 0000000..24b0daa
--- /dev/null
@@ -0,0 +1,6 @@
+TARGET         := slab_tests$(E)
+SOURCES                := slab_tests.c
+
+TGT_LDLIBS     := $(LIBS) $(GPERFTOOLS_LIBS)
+TGT_LDFLAGS    := $(LDFLAGS) $(GPERFTOOLS_LDFLAGS)
+TGT_PREREQS    := libfreeradius-util$(L)