diff options
Diffstat (limited to 'lib/isc/tests/quota_test.c')
-rw-r--r-- | lib/isc/tests/quota_test.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/lib/isc/tests/quota_test.c b/lib/isc/tests/quota_test.c new file mode 100644 index 0000000..854afd9 --- /dev/null +++ b/lib/isc/tests/quota_test.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/quota.h> +#include <isc/result.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "isctest.h" + +static void +isc_quota_get_set_test(void **state) { + UNUSED(state); + isc_quota_t quota; + isc_quota_t *quota2 = NULL; + isc_quota_init("a, 100); + + assert_int_equal(isc_quota_getmax("a), 100); + assert_int_equal(isc_quota_getsoft("a), 0); + + isc_quota_max("a, 50); + isc_quota_soft("a, 30); + + assert_int_equal(isc_quota_getmax("a), 50); + assert_int_equal(isc_quota_getsoft("a), 30); + + assert_int_equal(isc_quota_getused("a), 0); + isc_quota_attach("a, "a2); + assert_int_equal(isc_quota_getused("a), 1); + isc_quota_detach("a2); + assert_int_equal(isc_quota_getused("a), 0); + isc_quota_destroy("a); +} + +static void +add_quota(isc_quota_t *source, isc_quota_t **target, + isc_result_t expected_result, int expected_used) { + isc_result_t result; + + *target = NULL; + + result = isc_quota_attach(source, target); + assert_int_equal(result, expected_result); + + switch (expected_result) { + case ISC_R_SUCCESS: + case ISC_R_SOFTQUOTA: + assert_ptr_equal(*target, source); + break; + default: + assert_null(*target); + break; + } + + assert_int_equal(isc_quota_getused(source), expected_used); +} + +static void +isc_quota_hard_test(void **state) { + isc_quota_t quota; + isc_quota_t *quotas[110]; + int i; + UNUSED(state); + + isc_quota_init("a, 100); + + for (i = 0; i < 100; i++) { + add_quota("a, "as[i], ISC_R_SUCCESS, i + 1); + } + + add_quota("a, "as[100], ISC_R_QUOTA, 100); + + assert_int_equal(isc_quota_getused("a), 100); + + isc_quota_detach("as[0]); + assert_null(quotas[0]); + + add_quota("a, "as[100], ISC_R_SUCCESS, 100); + add_quota("a, "as[101], ISC_R_QUOTA, 100); + + for (i = 100; i > 0; i--) { + isc_quota_detach("as[i]); + assert_null(quotas[i]); + assert_int_equal(isc_quota_getused("a), i - 1); + } + assert_int_equal(isc_quota_getused("a), 0); + isc_quota_destroy("a); +} + +static void +isc_quota_soft_test(void **state) { + isc_quota_t quota; + isc_quota_t *quotas[110]; + int i; + UNUSED(state); + + isc_quota_init("a, 100); + isc_quota_soft("a, 50); + + for (i = 0; i < 50; i++) { + add_quota("a, "as[i], ISC_R_SUCCESS, i + 1); + } + for (i = 50; i < 100; i++) { + add_quota("a, "as[i], ISC_R_SOFTQUOTA, i + 1); + } + + add_quota("a, "as[i], ISC_R_QUOTA, 100); + + for (i = 99; i >= 0; i--) { + isc_quota_detach("as[i]); + assert_null(quotas[i]); + assert_int_equal(isc_quota_getused("a), i); + } + assert_int_equal(isc_quota_getused("a), 0); + isc_quota_destroy("a); +} + +static atomic_uint_fast32_t cb_calls = 0; +static isc_quota_cb_t cbs[30]; +static isc_quota_t *qp; + +static void +callback(isc_quota_t *quota, void *data) { + int val = *(int *)data; + /* Callback is not called if we get the quota directly */ + assert_int_not_equal(val, -1); + + /* We get the proper quota pointer */ + assert_ptr_equal(quota, qp); + + /* Verify that the callbacks are called in order */ + int v = atomic_fetch_add_relaxed(&cb_calls, 1); + assert_int_equal(v, val); + + /* + * First 5 will be detached by the test function, + * for the last 5 - do a 'chain detach'. + */ + if (v >= 5) { + isc_quota_detach("a); + } +} + +static void +isc_quota_callback_test(void **state) { + isc_result_t result; + isc_quota_t quota; + isc_quota_t *quotas[30]; + qp = "a; + /* + * - 10 calls that end with SUCCESS + * - 10 calls that end with SOFTQUOTA + * - 10 callbacks + */ + int ints[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + int i; + UNUSED(state); + + isc_quota_init("a, 20); + isc_quota_soft("a, 10); + + for (i = 0; i < 10; i++) { + quotas[i] = NULL; + isc_quota_cb_init(&cbs[i], callback, &ints[i]); + result = isc_quota_attach_cb("a, "as[i], &cbs[i]); + assert_int_equal(result, ISC_R_SUCCESS); + assert_ptr_equal(quotas[i], "a); + assert_int_equal(isc_quota_getused("a), i + 1); + } + for (i = 10; i < 20; i++) { + quotas[i] = NULL; + isc_quota_cb_init(&cbs[i], callback, &ints[i]); + result = isc_quota_attach_cb("a, "as[i], &cbs[i]); + assert_int_equal(result, ISC_R_SOFTQUOTA); + assert_ptr_equal(quotas[i], "a); + assert_int_equal(isc_quota_getused("a), i + 1); + } + + for (i = 20; i < 30; i++) { + quotas[i] = NULL; + isc_quota_cb_init(&cbs[i], callback, &ints[i]); + result = isc_quota_attach_cb("a, "as[i], &cbs[i]); + assert_int_equal(result, ISC_R_QUOTA); + assert_ptr_equal(quotas[i], NULL); + assert_int_equal(isc_quota_getused("a), 20); + } + assert_int_equal(atomic_load(&cb_calls), 0); + + for (i = 0; i < 5; i++) { + isc_quota_detach("as[i]); + assert_null(quotas[i]); + assert_int_equal(isc_quota_getused("a), 20); + assert_int_equal(atomic_load(&cb_calls), i + 1); + } + /* That should cause a chain reaction */ + isc_quota_detach("as[5]); + assert_int_equal(atomic_load(&cb_calls), 10); + + /* Release the quotas that we did not released in the callback */ + for (i = 0; i < 5; i++) { + qp = "a; + isc_quota_detach(&qp); + } + + for (i = 6; i < 20; i++) { + isc_quota_detach("as[i]); + assert_null(quotas[i]); + assert_int_equal(isc_quota_getused("a), 19 - i); + } + assert_int_equal(atomic_load(&cb_calls), 10); + + assert_int_equal(isc_quota_getused("a), 0); + isc_quota_destroy("a); +} + +/* + * Multithreaded quota callback test: + * - quota set to 100 + * - 10 threads, each trying to get 100 quotas. + * - creates a separate thread to release it after 10ms + */ + +typedef struct qthreadinfo { + atomic_uint_fast32_t direct; + atomic_uint_fast32_t callback; + isc_quota_t *quota; + isc_quota_cb_t callbacks[100]; +} qthreadinfo_t; + +static atomic_uint_fast32_t g_tnum = 0; +/* at most 10 * 100 quota_detach threads */ +isc_thread_t g_threads[10 * 100]; + +static void * +quota_detach(void *quotap) { + isc_quota_t *quota = (isc_quota_t *)quotap; + isc_test_nap(10000); + isc_quota_detach("a); + return ((isc_threadresult_t)0); +} + +static void +quota_callback(isc_quota_t *quota, void *data) { + qthreadinfo_t *qti = (qthreadinfo_t *)data; + atomic_fetch_add_relaxed(&qti->callback, 1); + int tnum = atomic_fetch_add_relaxed(&g_tnum, 1); + isc_thread_create(quota_detach, quota, &g_threads[tnum]); +} + +static isc_threadresult_t +quota_thread(void *qtip) { + qthreadinfo_t *qti = (qthreadinfo_t *)qtip; + for (int i = 0; i < 100; i++) { + isc_quota_cb_init(&qti->callbacks[i], quota_callback, qti); + isc_quota_t *quota = NULL; + isc_result_t result = isc_quota_attach_cb(qti->quota, "a, + &qti->callbacks[i]); + if (result == ISC_R_SUCCESS) { + atomic_fetch_add_relaxed(&qti->direct, 1); + int tnum = atomic_fetch_add_relaxed(&g_tnum, 1); + isc_thread_create(quota_detach, quota, + &g_threads[tnum]); + } + } + return ((isc_threadresult_t)0); +} + +static void +isc_quota_callback_mt_test(void **state) { + UNUSED(state); + isc_quota_t quota; + int i; + + isc_quota_init("a, 100); + static qthreadinfo_t qtis[10]; + isc_thread_t threads[10]; + for (i = 0; i < 10; i++) { + atomic_init(&qtis[i].direct, 0); + atomic_init(&qtis[i].callback, 0); + qtis[i].quota = "a; + isc_thread_create(quota_thread, &qtis[i], &threads[i]); + } + for (i = 0; i < 10; i++) { + isc_thread_join(threads[i], NULL); + } + + for (i = 0; i < (int)atomic_load(&g_tnum); i++) { + isc_thread_join(g_threads[i], NULL); + } + int direct = 0, callback = 0; + + for (i = 0; i < 10; i++) { + direct += atomic_load(&qtis[i].direct); + callback += atomic_load(&qtis[i].callback); + } + /* Total quota gained must be 10 threads * 100 tries */ + assert_int_equal(direct + callback, 10 * 100); + /* + * At least 100 must be direct, the rest is virtually random: + * - in a regular run I'm constantly getting 100:900 ratio + * - under rr - usually around ~120:880 + * - under rr -h - 1000:0 + */ + assert_true(direct >= 100); + + isc_quota_destroy("a); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_quota_get_set_test), + cmocka_unit_test(isc_quota_hard_test), + cmocka_unit_test(isc_quota_soft_test), + cmocka_unit_test(isc_quota_callback_test), + cmocka_unit_test(isc_quota_callback_mt_test), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include <stdio.h> + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ |