From f8217d240d598f39f70047f7a623dd46312542c6 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 4 Dec 2021 16:01:12 -0800 Subject: Initial commit. --- listpool/test/listpool_test.c | 166 +++++++++++++++++++++++++++++++++++++ listpool/test/test.h | 185 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 listpool/test/listpool_test.c create mode 100644 listpool/test/test.h (limited to 'listpool/test') diff --git a/listpool/test/listpool_test.c b/listpool/test/listpool_test.c new file mode 100644 index 0000000..cb54d00 --- /dev/null +++ b/listpool/test/listpool_test.c @@ -0,0 +1,166 @@ +#include "listpool.h" + +#include "test.h" + +#define NUM_BLOCKS 10 + +DEF_LISTPOOL(test_pool, int, NUM_BLOCKS); + +static int count(test_pool* pool) { + int count = 0; + listpool_foreach(pool, n, { count++; }); + return count; +} + +static int sum(test_pool* pool) { + int sum = 0; + listpool_foreach(pool, n, { sum += *n; }); + return sum; +} + +// Create a pool. +TEST_CASE(listpool_create) { + test_pool pool; + listpool_make(&pool); +} + +// Allocate all N blocks. +TEST_CASE(listpool_fully_allocate) { + test_pool pool; + listpool_make(&pool); + + for (int i = 0; i < NUM_BLOCKS; ++i) { + const int* block = listpool_alloc(&pool); + TEST_TRUE(block != 0); + } +} + +// Allocate all N blocks, then free them. +TEST_CASE(listpool_fill_then_free) { + test_pool pool; + listpool_make(&pool); + + int* blocks[NUM_BLOCKS] = {0}; + for (int i = 0; i < NUM_BLOCKS; i++) { + blocks[i] = listpool_alloc(&pool); + TEST_TRUE(blocks[i] != 0); + } + + for (int i = 0; i < NUM_BLOCKS; i++) { + listpool_free(&pool, &blocks[i]); + TEST_EQUAL(blocks[i], 0); // Pointer should be set to 0 on free. + } + + TEST_EQUAL(count(&pool), 0); +} + +// Attempt to allocate blocks past the maximum pool size. +// The pool should handle the failed allocations gracefully. +TEST_CASE(listpool_allocate_beyond_max_size) { + test_pool pool; + listpool_make(&pool); + + // Fully allocate the pool. + for (int i = 0; i < NUM_BLOCKS; ++i) { + TEST_TRUE(listpool_alloc(&pool) != 0); + } + + // Past the end. + for (int i = 0; i < NUM_BLOCKS; ++i) { + TEST_EQUAL(listpool_alloc(&pool), 0); + } +} + +// Free blocks should always remain zeroed out. +// This tests the invariant right after creating the pool. +TEST_CASE(listpool_zero_free_blocks_after_creation) { + test_pool pool; + listpool_make(&pool); + + const int zero = 0; + for (int i = 0; i < NUM_BLOCKS; ++i) { + const int* block = (const int*)(pool.blocks) + i; + TEST_EQUAL(memcmp(block, &zero, sizeof(int)), 0); + } +} + +// Free blocks should always remain zeroed out. +// This tests the invariant after freeing a block. +TEST_CASE(listpool_zero_free_block_after_free) { + test_pool pool; + listpool_make(&pool); + + int* val = listpool_alloc(&pool); + TEST_TRUE(val != 0); + *val = 177; + + int* old_val = val; + listpool_free(&pool, &val); // val pointer is set to 0. + TEST_EQUAL(*old_val, 0); // Block is zeroed out after free. +} + +// Traverse an empty pool. +TEST_CASE(listpool_traverse_empty) { + test_pool pool; + listpool_make(&pool); + + TEST_EQUAL(count(&pool), 0); +} + +// Traverse a partially full pool. +TEST_CASE(listpool_traverse_partially_full) { + const int N = NUM_BLOCKS / 2; + + test_pool pool; + listpool_make(&pool); + + for (int i = 0; i < N; ++i) { + int* val = listpool_alloc(&pool); + TEST_TRUE(val != 0); + *val = i + 1; + } + + TEST_EQUAL(sum(&pool), (N) * (N + 1) / 2); +} + +// Traverse a full pool. +TEST_CASE(listpool_traverse_full) { + test_pool pool; + listpool_make(&pool); + + for (int i = 0; i < NUM_BLOCKS; ++i) { + int* val = listpool_alloc(&pool); + TEST_TRUE(val != 0); + *val = i + 1; + } + + TEST_EQUAL(sum(&pool), (NUM_BLOCKS) * (NUM_BLOCKS + 1) / 2); +} + +// Remove a value from the list. +TEST_CASE(listpool_remove_value) { + test_pool pool; + listpool_make(&pool); + + int* x = listpool_alloc(&pool); + int* y = listpool_alloc(&pool); + TEST_TRUE(x != 0); + TEST_TRUE(y != 0); + + *x = 155; + *y = 177; + + listpool_remove(&pool, 155); // x + + TEST_EQUAL(count(&pool), 1); + TEST_EQUAL(sum(&pool), *y); +} + +// Stress test. +// +// 1. Allocate the pool, either fully or partially. If fully, attempt to +// allocate some items past the end. +// +// 2. Free all allocated items in some random order. + +int main() { return 0; } diff --git a/listpool/test/test.h b/listpool/test/test.h new file mode 100644 index 0000000..fd8dc22 --- /dev/null +++ b/listpool/test/test.h @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#ifdef UNIT_TEST + +#include +#include +#include +#include + +#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ + defined(__NetBSD__) || defined(__OpenBSD__) +#define USE_SYSCTL_FOR_ARGS 1 +// clang-format off +#include +#include +// clang-format on +#include // getpid +#endif + +struct test_file_metadata; + +struct test_failure { + bool present; + const char *message; + const char *file; + int line; +}; + +struct test_case_metadata { + void (*fn)(struct test_case_metadata *, struct test_file_metadata *); + struct test_failure failure; + const char *name; + struct test_case_metadata *next; +}; + +struct test_file_metadata { + bool registered; + const char *name; + struct test_file_metadata *next; + struct test_case_metadata *tests; +}; + +struct test_file_metadata __attribute__((weak)) * test_file_head; + +#define SET_FAILURE(_message) \ + metadata->failure = (struct test_failure) { \ + .message = _message, .file = __FILE__, .line = __LINE__, .present = true, \ + } + +#define TEST_EQUAL(a, b) \ + do { \ + if ((a) != (b)) { \ + SET_FAILURE(#a " != " #b); \ + return; \ + } \ + } while (0) + +#define TEST_TRUE(a) \ + do { \ + if (!(a)) { \ + SET_FAILURE(#a " is not true"); \ + return; \ + } \ + } while (0) + +#define TEST_STREQUAL(a, b) \ + do { \ + if (strcmp(a, b) != 0) { \ + SET_FAILURE(#a " != " #b); \ + return; \ + } \ + } while (0) + +#define TEST_CASE(_name) \ + static void __test_h_##_name(struct test_case_metadata *, \ + struct test_file_metadata *); \ + static struct test_file_metadata __test_h_file; \ + static struct test_case_metadata __test_h_meta_##_name = { \ + .name = #_name, \ + .fn = __test_h_##_name, \ + }; \ + static void __attribute__((constructor(101))) __test_h_##_name##_register(void) { \ + __test_h_meta_##_name.next = __test_h_file.tests; \ + __test_h_file.tests = &__test_h_meta_##_name; \ + if (!__test_h_file.registered) { \ + __test_h_file.name = __FILE__; \ + __test_h_file.next = test_file_head; \ + test_file_head = &__test_h_file; \ + __test_h_file.registered = true; \ + } \ + } \ + static void __test_h_##_name( \ + struct test_case_metadata *metadata __attribute__((unused)), \ + struct test_file_metadata *file_metadata __attribute__((unused))) + +extern void __attribute__((weak)) (*test_h_unittest_setup)(void); +/// Run defined tests, return true if all tests succeeds +/// @param[out] tests_run if not NULL, set to whether tests were run +static inline void __attribute__((constructor(102))) run_tests(void) { + bool should_run = false; +#ifdef USE_SYSCTL_FOR_ARGS + int mib[] = { + CTL_KERN, +#if defined(__NetBSD__) || defined(__OpenBSD__) + KERN_PROC_ARGS, + getpid(), + KERN_PROC_ARGV, +#else + KERN_PROC, + KERN_PROC_ARGS, + getpid(), +#endif + }; + char *arg = NULL; + size_t arglen; + sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &arglen, NULL, 0); + arg = malloc(arglen); + sysctl(mib, sizeof(mib) / sizeof(mib[0]), arg, &arglen, NULL, 0); +#else + FILE *cmdlinef = fopen("/proc/self/cmdline", "r"); + char *arg = NULL; + int arglen; + fscanf(cmdlinef, "%ms%n", &arg, &arglen); + fclose(cmdlinef); +#endif + for (char *pos = arg; pos < arg + arglen; pos += strlen(pos) + 1) { + if (strcmp(pos, "--unittest") == 0) { + should_run = true; + break; + } + } + free(arg); + + if (!should_run) { + return; + } + + if (&test_h_unittest_setup) { + test_h_unittest_setup(); + } + + struct test_file_metadata *i = test_file_head; + int failed = 0, success = 0; + while (i) { + fprintf(stderr, "Running tests from %s:\n", i->name); + struct test_case_metadata *j = i->tests; + while (j) { + fprintf(stderr, "\t%s ... ", j->name); + j->failure.present = false; + j->fn(j, i); + if (j->failure.present) { + fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message, + j->failure.file, j->failure.line); + failed++; + } else { + fprintf(stderr, "passed\n"); + success++; + } + j = j->next; + } + fprintf(stderr, "\n"); + i = i->next; + } + int total = failed + success; + fprintf(stderr, "Test results: passed %d/%d, failed %d/%d\n", success, total, + failed, total); + exit(failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE); +} + +#else + +#include + +#define TEST_CASE(name) static void __attribute__((unused)) __test_h_##name(void) + +#define TEST_EQUAL(a, b) \ + (void)(a); \ + (void)(b) +#define TEST_TRUE(a) (void)(a) +#define TEST_STREQUAL(a, b) \ + (void)(a); \ + (void)(b) + +#endif -- cgit v1.2.3