summaryrefslogtreecommitdiffstats
path: root/lib/isc/tests/quota_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/isc/tests/quota_test.c')
-rw-r--r--lib/isc/tests/quota_test.c358
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(&quota, 100);
+
+ assert_int_equal(isc_quota_getmax(&quota), 100);
+ assert_int_equal(isc_quota_getsoft(&quota), 0);
+
+ isc_quota_max(&quota, 50);
+ isc_quota_soft(&quota, 30);
+
+ assert_int_equal(isc_quota_getmax(&quota), 50);
+ assert_int_equal(isc_quota_getsoft(&quota), 30);
+
+ assert_int_equal(isc_quota_getused(&quota), 0);
+ isc_quota_attach(&quota, &quota2);
+ assert_int_equal(isc_quota_getused(&quota), 1);
+ isc_quota_detach(&quota2);
+ assert_int_equal(isc_quota_getused(&quota), 0);
+ isc_quota_destroy(&quota);
+}
+
+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(&quota, 100);
+
+ for (i = 0; i < 100; i++) {
+ add_quota(&quota, &quotas[i], ISC_R_SUCCESS, i + 1);
+ }
+
+ add_quota(&quota, &quotas[100], ISC_R_QUOTA, 100);
+
+ assert_int_equal(isc_quota_getused(&quota), 100);
+
+ isc_quota_detach(&quotas[0]);
+ assert_null(quotas[0]);
+
+ add_quota(&quota, &quotas[100], ISC_R_SUCCESS, 100);
+ add_quota(&quota, &quotas[101], ISC_R_QUOTA, 100);
+
+ for (i = 100; i > 0; i--) {
+ isc_quota_detach(&quotas[i]);
+ assert_null(quotas[i]);
+ assert_int_equal(isc_quota_getused(&quota), i - 1);
+ }
+ assert_int_equal(isc_quota_getused(&quota), 0);
+ isc_quota_destroy(&quota);
+}
+
+static void
+isc_quota_soft_test(void **state) {
+ isc_quota_t quota;
+ isc_quota_t *quotas[110];
+ int i;
+ UNUSED(state);
+
+ isc_quota_init(&quota, 100);
+ isc_quota_soft(&quota, 50);
+
+ for (i = 0; i < 50; i++) {
+ add_quota(&quota, &quotas[i], ISC_R_SUCCESS, i + 1);
+ }
+ for (i = 50; i < 100; i++) {
+ add_quota(&quota, &quotas[i], ISC_R_SOFTQUOTA, i + 1);
+ }
+
+ add_quota(&quota, &quotas[i], ISC_R_QUOTA, 100);
+
+ for (i = 99; i >= 0; i--) {
+ isc_quota_detach(&quotas[i]);
+ assert_null(quotas[i]);
+ assert_int_equal(isc_quota_getused(&quota), i);
+ }
+ assert_int_equal(isc_quota_getused(&quota), 0);
+ isc_quota_destroy(&quota);
+}
+
+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(&quota);
+ }
+}
+
+static void
+isc_quota_callback_test(void **state) {
+ isc_result_t result;
+ isc_quota_t quota;
+ isc_quota_t *quotas[30];
+ qp = &quota;
+ /*
+ * - 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(&quota, 20);
+ isc_quota_soft(&quota, 10);
+
+ for (i = 0; i < 10; i++) {
+ quotas[i] = NULL;
+ isc_quota_cb_init(&cbs[i], callback, &ints[i]);
+ result = isc_quota_attach_cb(&quota, &quotas[i], &cbs[i]);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_ptr_equal(quotas[i], &quota);
+ assert_int_equal(isc_quota_getused(&quota), 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(&quota, &quotas[i], &cbs[i]);
+ assert_int_equal(result, ISC_R_SOFTQUOTA);
+ assert_ptr_equal(quotas[i], &quota);
+ assert_int_equal(isc_quota_getused(&quota), 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(&quota, &quotas[i], &cbs[i]);
+ assert_int_equal(result, ISC_R_QUOTA);
+ assert_ptr_equal(quotas[i], NULL);
+ assert_int_equal(isc_quota_getused(&quota), 20);
+ }
+ assert_int_equal(atomic_load(&cb_calls), 0);
+
+ for (i = 0; i < 5; i++) {
+ isc_quota_detach(&quotas[i]);
+ assert_null(quotas[i]);
+ assert_int_equal(isc_quota_getused(&quota), 20);
+ assert_int_equal(atomic_load(&cb_calls), i + 1);
+ }
+ /* That should cause a chain reaction */
+ isc_quota_detach(&quotas[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 = &quota;
+ isc_quota_detach(&qp);
+ }
+
+ for (i = 6; i < 20; i++) {
+ isc_quota_detach(&quotas[i]);
+ assert_null(quotas[i]);
+ assert_int_equal(isc_quota_getused(&quota), 19 - i);
+ }
+ assert_int_equal(atomic_load(&cb_calls), 10);
+
+ assert_int_equal(isc_quota_getused(&quota), 0);
+ isc_quota_destroy(&quota);
+}
+
+/*
+ * 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(&quota);
+ 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, &quota,
+ &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(&quota, 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 = &quota;
+ 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(&quota);
+}
+
+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 */