--- a/tests/test_mempool.c Fri May 23 12:44:24 2025 +0200 +++ b/tests/test_mempool.c Fri May 23 13:36:11 2025 +0200 @@ -31,6 +31,19 @@ #include "cx/mempool.h" +#include <errno.h> + +static unsigned test_mempool_destructor_called; + +static void test_mempool_destructor(cx_attr_unused void *mem) { + test_mempool_destructor_called++; +} + +static void test_mempool_destructor2(void *data, cx_attr_unused void *mem) { + int *ctr = data; + *ctr = *ctr + 1; +} + CX_TEST(test_mempool_create) { CxMempool *pool = cxMempoolCreateSimple(16); CX_TEST_DO { @@ -51,21 +64,69 @@ cxMempoolFree(pool); } +static CX_TEST_SUBROUTINE(test_mempool_malloc_verify, CxMempool *pool) { + CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); + CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); + CX_TEST_ASSERT(pool->size == 2); + CX_TEST_ASSERT(pool->capacity == 4); + CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); + CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); + CX_TEST_ASSERT(pool->size == 4); + CX_TEST_ASSERT(pool->capacity == 4); + CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); + int *i = cxMalloc(pool->allocator, sizeof(int)); + CX_TEST_ASSERT(i != NULL); + *i = 4083914; // let asan / valgrind check + CX_TEST_ASSERT(pool->size == 6); + CX_TEST_ASSERT(pool->capacity >= 6); +} + +CX_TEST(test_mempool_malloc0) { + CxMempool *pool = cxMempoolCreatePure(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_malloc_verify, pool); + } + cxMempoolFree(pool); +} + CX_TEST(test_mempool_malloc) { CxMempool *pool = cxMempoolCreateSimple(4); CX_TEST_DO { - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(pool->size == 2); - CX_TEST_ASSERT(pool->capacity == 4); - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(pool->size == 4); - CX_TEST_ASSERT(pool->capacity == 4); - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(pool->size == 6); - CX_TEST_ASSERT(pool->capacity >= 6); + CX_TEST_CALL_SUBROUTINE(test_mempool_malloc_verify, pool); + } + cxMempoolFree(pool); +} + +CX_TEST(test_mempool_malloc2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_malloc_verify, pool); + } + cxMempoolFree(pool); +} + +static CX_TEST_SUBROUTINE(test_mempool_calloc_verify, CxMempool *pool) { + int *test = cxCalloc(pool->allocator, 2, sizeof(int)); + CX_TEST_ASSERT(test != NULL); + CX_TEST_ASSERT(test[0] == 0); + CX_TEST_ASSERT(test[1] == 0); +#if __GNUC__ > 11 +// we want to explicitly test the overflow +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Walloc-size-larger-than=" +#endif + errno = 0; + CX_TEST_ASSERT(NULL == cxCalloc(pool->allocator, SIZE_MAX / 2, sizeof(int))); + CX_TEST_ASSERT(errno == EOVERFLOW); +#if __GNUC__ > 11 +#pragma GCC diagnostic pop +#endif +} + +CX_TEST(test_mempool_calloc0) { + CxMempool *pool = cxMempoolCreatePure(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_calloc_verify, pool); } cxMempoolFree(pool); } @@ -73,66 +134,121 @@ CX_TEST(test_mempool_calloc) { CxMempool *pool = cxMempoolCreateSimple(4); CX_TEST_DO { - int *test = cxCalloc(pool->allocator, 2, sizeof(int)); - CX_TEST_ASSERT(test != NULL); - CX_TEST_ASSERT(test[0] == 0); - CX_TEST_ASSERT(test[1] == 0); + CX_TEST_CALL_SUBROUTINE(test_mempool_calloc_verify, pool); + } + cxMempoolFree(pool); +} + +CX_TEST(test_mempool_calloc2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_calloc_verify, pool); } cxMempoolFree(pool); } -static unsigned test_mempool_destructor_called; +static CX_TEST_SUBROUTINE(test_mempool_realloc_verify, CxMempool *pool, enum cx_mempool_type type) { + // use realloc with NULL which shall behave as a malloc + int *data = cxRealloc(pool->allocator, NULL, 2*sizeof(int)); + if (type == CX_MEMPOOL_TYPE_SIMPLE) { + cxMempoolSetDestructor(data, test_mempool_destructor); + } else if (type == CX_MEMPOOL_TYPE_ADVANCED) { + cxMempoolSetDestructor2(data, test_mempool_destructor2, &test_mempool_destructor_called); + } + *data = 13; + + // shrink to actual sizeof(int) + data = cxRealloc(pool->allocator, data, sizeof(int)); + CX_TEST_ASSERT(*data == 13); + + // realloc with the same size (should not do anything) + data = cxRealloc(pool->allocator, data, sizeof(int)); + CX_TEST_ASSERT(*data == 13); -static void test_mempool_destructor(cx_attr_unused void *mem) { - test_mempool_destructor_called++; + // now try hard to trigger a memmove + int *rdata = data; + unsigned n = 1; + while (rdata == data) { + n <<= 1; + // eventually the memory should be moved elsewhere + CX_TEST_ASSERTM(n < 65536, "Reallocation attempt failed - test not executable"); + rdata = cxRealloc(pool->allocator, data, n * sizeof(intptr_t)); + } + + CX_TEST_ASSERT(*rdata == 13); + // test if destructor is still intact + if (type != CX_MEMPOOL_TYPE_PURE) { + test_mempool_destructor_called = 0; + cxFree(pool->allocator, rdata); + CX_TEST_ASSERT(test_mempool_destructor_called == 1); + } +} + +CX_TEST(test_mempool_realloc0) { + CxMempool *pool = cxMempoolCreatePure(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_realloc_verify, pool, CX_MEMPOOL_TYPE_PURE); + } + cxMempoolFree(pool); } CX_TEST(test_mempool_realloc) { CxMempool *pool = cxMempoolCreateSimple(4); - cxMempoolGlobalDestructor(pool, test_mempool_destructor); CX_TEST_DO { - CX_TEST_ASSERT(pool->destr == test_mempool_destructor); - int *data = cxMalloc(pool->allocator, sizeof(int)); - *data = 13; + CX_TEST_CALL_SUBROUTINE(test_mempool_realloc_verify, pool, CX_MEMPOOL_TYPE_SIMPLE); + } + cxMempoolFree(pool); +} - int *rdata = data; - unsigned n = 1; - while (rdata == data) { - n <<= 1; - // eventually the memory should be moved elsewhere - CX_TEST_ASSERTM(n < 65536, "Reallocation attempt failed - test not executable"); - rdata = cxRealloc(pool->allocator, data, n * sizeof(intptr_t)); - } - - CX_TEST_ASSERT(*rdata == 13); - // test if destructor is still intact - test_mempool_destructor_called = 0; - cxFree(pool->allocator, rdata); - CX_TEST_ASSERT(test_mempool_destructor_called == 1); +CX_TEST(test_mempool_realloc2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_realloc_verify, pool, CX_MEMPOOL_TYPE_ADVANCED); } cxMempoolFree(pool); } +static CX_TEST_SUBROUTINE(test_mempool_free_verify, CxMempool *pool) { + void *mem1, *mem2; + mem1 = cxMalloc(pool->allocator, 16); + cxFree(pool->allocator, mem1); + CX_TEST_ASSERT(pool->size == 0); + + mem1 = cxMalloc(pool->allocator, 16); + mem1 = cxMalloc(pool->allocator, 16); + mem1 = cxMalloc(pool->allocator, 16); + mem2 = cxMalloc(pool->allocator, 16); + mem2 = cxMalloc(pool->allocator, 16); + + CX_TEST_ASSERT(pool->size == 5); + // a realloc with size zero shall behave like a free + void *freed = cxRealloc(pool->allocator, mem1, 0); + CX_TEST_ASSERT(freed == NULL); + CX_TEST_ASSERT(pool->size == 4); + cxFree(pool->allocator, mem2); + CX_TEST_ASSERT(pool->size == 3); +} + +CX_TEST(test_mempool_free0) { + CxMempool *pool = cxMempoolCreatePure(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_free_verify, pool); + } + cxMempoolFree(pool); +} CX_TEST(test_mempool_free) { CxMempool *pool = cxMempoolCreateSimple(4); - void *mem1, *mem2; CX_TEST_DO { - mem1 = cxMalloc(pool->allocator, 16); - cxFree(pool->allocator, mem1); - CX_TEST_ASSERT(pool->size == 0); + CX_TEST_CALL_SUBROUTINE(test_mempool_free_verify, pool); + } + cxMempoolFree(pool); +} - mem1 = cxMalloc(pool->allocator, 16); - mem1 = cxMalloc(pool->allocator, 16); - mem1 = cxMalloc(pool->allocator, 16); - mem2 = cxMalloc(pool->allocator, 16); - mem2 = cxMalloc(pool->allocator, 16); - - CX_TEST_ASSERT(pool->size == 5); - cxFree(pool->allocator, mem1); - CX_TEST_ASSERT(pool->size == 4); - cxFree(pool->allocator, mem2); - CX_TEST_ASSERT(pool->size == 3); +CX_TEST(test_mempool_free2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_free_verify, pool); } cxMempoolFree(pool); } @@ -154,14 +270,124 @@ } } +CX_TEST(test_mempool_destroy2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + int *data = cxMalloc(pool->allocator, sizeof(int)); + int ctr = 0; + *data = 47; + cxMempoolSetDestructor2(data, test_mempool_destructor2, &ctr); + CX_TEST_ASSERT(*data == 47); + cxFree(pool->allocator, data); + CX_TEST_ASSERT(ctr == 1); + data = cxMalloc(pool->allocator, sizeof(int)); + cxMempoolSetDestructor2(data, test_mempool_destructor2, &ctr); + cxMempoolFree(pool); + CX_TEST_ASSERT(ctr == 2); + } +} + +CX_TEST(test_mempool_remove_destructor) { + CxMempool *pool = cxMempoolCreateSimple(4); + CX_TEST_DO { + int *data = cxMalloc(pool->allocator, sizeof(int)); + *data = 13; + cxMempoolSetDestructor(data, test_mempool_destructor); + CX_TEST_ASSERT(*data == 13); + cxMempoolRemoveDestructor(data); + CX_TEST_ASSERT(*data == 13); + test_mempool_destructor_called = 0; + cxFree(pool->allocator, data); + CX_TEST_ASSERT(test_mempool_destructor_called == 0); + data = cxMalloc(pool->allocator, sizeof(int)); + *data = 99; + cxMempoolSetDestructor(data, test_mempool_destructor); + cxMempoolRemoveDestructor(data); + CX_TEST_ASSERT(*data == 99); + cxMempoolFree(pool); + CX_TEST_ASSERT(test_mempool_destructor_called == 0); + } +} + +CX_TEST(test_mempool_remove_destructor2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + int *data = cxMalloc(pool->allocator, sizeof(int)); + int ctr = 0; + *data = 47; + cxMempoolSetDestructor2(data, test_mempool_destructor2, &ctr); + CX_TEST_ASSERT(*data == 47); + cxMempoolRemoveDestructor2(data); + CX_TEST_ASSERT(*data == 47); + cxFree(pool->allocator, data); + CX_TEST_ASSERT(ctr == 0); + data = cxMalloc(pool->allocator, sizeof(int)); + *data = 99; + cxMempoolSetDestructor2(data, test_mempool_destructor2, &ctr); + cxMempoolRemoveDestructor2(data); + CX_TEST_ASSERT(*data == 99); + cxMempoolFree(pool); + CX_TEST_ASSERT(ctr == 0); + } +} + +static CX_TEST_SUBROUTINE(test_mempool_global_destructors_verify, CxMempool *pool) { + int *data = cxMalloc(pool->allocator, sizeof(int)); + int ctr = 0; + cxMempoolGlobalDestructor(pool, test_mempool_destructor); + cxMempoolGlobalDestructor2(pool, test_mempool_destructor2, &ctr); + test_mempool_destructor_called = 0; + cxFree(pool->allocator, data); + CX_TEST_ASSERT(ctr == 1); + CX_TEST_ASSERT(test_mempool_destructor_called == 1); + data = cxMalloc(pool->allocator, sizeof(int)); + cxMempoolFree(pool); + CX_TEST_ASSERT(ctr == 2); + CX_TEST_ASSERT(test_mempool_destructor_called == 2); +} + +CX_TEST(test_mempool_global_destructors0) { + CxMempool *pool = cxMempoolCreatePure(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_global_destructors_verify, pool); + } +} + +CX_TEST(test_mempool_global_destructors) { + CxMempool *pool = cxMempoolCreateSimple(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_global_destructors_verify, pool); + } +} + +CX_TEST(test_mempool_global_destructors2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_global_destructors_verify, pool); + } +} + CX_TEST(test_mempool_register) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + int *data = cxMalloc(pool->allocator, sizeof(int)); + test_mempool_destructor_called = 0; + cxMempoolSetDestructor2(data, test_mempool_destructor2, &test_mempool_destructor_called); + int donotfree = 0; + cxMempoolRegister(pool, &donotfree, test_mempool_destructor); + cxMempoolFree(pool); + CX_TEST_ASSERT(test_mempool_destructor_called == 2); + } +} + +CX_TEST(test_mempool_register2) { CxMempool *pool = cxMempoolCreateSimple(4); CX_TEST_DO { int *data = cxMalloc(pool->allocator, sizeof(int)); test_mempool_destructor_called = 0; cxMempoolSetDestructor(data, test_mempool_destructor); int donotfree = 0; - cxMempoolRegister(pool, &donotfree, test_mempool_destructor); + cxMempoolRegister2(pool, &donotfree, test_mempool_destructor2, &test_mempool_destructor_called); cxMempoolFree(pool); CX_TEST_ASSERT(test_mempool_destructor_called == 2); } @@ -222,50 +448,75 @@ CxMempool *src = cxMempoolCreateSimple(4); CxMempool *dest = cxMempoolCreateSimple(4); CX_TEST_DO { + int *a = cxMalloc(src->allocator, sizeof(int)); int *b = cxMalloc(src->allocator, sizeof(int)); - b = cxMalloc(src->allocator, sizeof(int)); int *c = malloc(sizeof(int)); cxMempoolRegister(src, c, free); + int *d = malloc(sizeof(int)); + cxMempoolRegister(src, d, free); CX_TEST_ASSERT(src->size == 2); - CX_TEST_ASSERT(src->registered_size == 1); + CX_TEST_ASSERT(src->registered_size == 2); - int result = cxMempoolTransferObject(src, dest, b); + int result = cxMempoolTransferObject(src, dest, a); CX_TEST_ASSERT(result == 0); CX_TEST_ASSERT(src->size == 1); + CX_TEST_ASSERT(src->registered_size == 2); CX_TEST_ASSERT(dest->size == 1); - result = cxMempoolTransferObject(src, dest, b); + CX_TEST_ASSERT(dest->registered_size == 0); + result = cxMempoolTransferObject(src, dest, a); + CX_TEST_ASSERT(result != 0); + CX_TEST_ASSERT(src->size == 1); + CX_TEST_ASSERT(src->registered_size == 2); + CX_TEST_ASSERT(dest->size == 1); + CX_TEST_ASSERT(dest->registered_size == 0); + + // can also transfer foreign memory this way + result = cxMempoolTransferObject(src, dest, c); + CX_TEST_ASSERT(result == 0); + CX_TEST_ASSERT(src->size == 1); + CX_TEST_ASSERT(src->registered_size == 1); + CX_TEST_ASSERT(dest->size == 1); + CX_TEST_ASSERT(dest->registered_size == 1); + + // src==dest is an error + result = cxMempoolTransferObject(dest, dest, b); CX_TEST_ASSERT(result != 0); CX_TEST_ASSERT(src->size == 1); CX_TEST_ASSERT(dest->size == 1); - // can also transfer foreign memory this way - CX_TEST_ASSERT(src->registered_size == 1); - CX_TEST_ASSERT(dest->registered_size == 0); - result = cxMempoolTransferObject(src, dest, c); - CX_TEST_ASSERT(result == 0); - CX_TEST_ASSERT(src->registered_size == 0); - CX_TEST_ASSERT(dest->registered_size == 1); - - result = cxMempoolTransferObject(dest, dest, b); - CX_TEST_ASSERT(result != 0); - CX_TEST_ASSERT(src->size == 1); - CX_TEST_ASSERT(dest->size == 1); + // check that we don't die when we free memory that's still in the source pool + cxFree(src->allocator, b); } cxMempoolFree(src); cxMempoolFree(dest); - // let valgrind check that everything worked + // let valgrind check that everything else worked } CxTestSuite *cx_test_suite_mempool(void) { CxTestSuite *suite = cx_test_suite_new("mempool"); cx_test_register(suite, test_mempool_create); + cx_test_register(suite, test_mempool_malloc0); + cx_test_register(suite, test_mempool_calloc0); + cx_test_register(suite, test_mempool_realloc0); + cx_test_register(suite, test_mempool_free0); cx_test_register(suite, test_mempool_malloc); cx_test_register(suite, test_mempool_calloc); cx_test_register(suite, test_mempool_realloc); cx_test_register(suite, test_mempool_free); cx_test_register(suite, test_mempool_destroy); + cx_test_register(suite, test_mempool_malloc2); + cx_test_register(suite, test_mempool_calloc2); + cx_test_register(suite, test_mempool_realloc2); + cx_test_register(suite, test_mempool_free2); + cx_test_register(suite, test_mempool_destroy2); + cx_test_register(suite, test_mempool_remove_destructor); + cx_test_register(suite, test_mempool_remove_destructor2); + cx_test_register(suite, test_mempool_global_destructors0); + cx_test_register(suite, test_mempool_global_destructors); + cx_test_register(suite, test_mempool_global_destructors2); cx_test_register(suite, test_mempool_register); + cx_test_register(suite, test_mempool_register2); cx_test_register(suite, test_mempool_transfer); cx_test_register(suite, test_mempool_transfer_object);