tests/test_mempool.c

Sat, 24 May 2025 00:04:11 +0200

author
mike@uapl01.fritz.box
date
Sat, 24 May 2025 00:04:11 +0200
changeset 1330
33c95cfc088e
parent 1328
2cf66dee40b8
permissions
-rw-r--r--

implement zalloc() - resolves #679

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "cx/test.h"
#include "util_allocator.h"

#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 {
        CX_TEST_ASSERT(pool->destr == NULL);
        CX_TEST_ASSERT(pool->destr2 == NULL);
        CX_TEST_ASSERT(pool->destr2_data == NULL);
        CX_TEST_ASSERT(pool->allocator != NULL);
        CX_TEST_ASSERT(pool->allocator->cl != NULL);
        CX_TEST_ASSERT(pool->allocator->data == pool);
        CX_TEST_ASSERT(pool->allocator->cl->malloc != NULL);
        CX_TEST_ASSERT(pool->allocator->cl->calloc != NULL);
        CX_TEST_ASSERT(pool->allocator->cl->realloc != NULL);
        CX_TEST_ASSERT(pool->allocator->cl->free != NULL);
        CX_TEST_ASSERT(pool->capacity == 16);
        CX_TEST_ASSERT(pool->size == 0);
        CX_TEST_ASSERT(pool->data != NULL);
    }
    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_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);
}

CX_TEST(test_mempool_calloc) {
    CxMempool *pool = cxMempoolCreateSimple(4);
    CX_TEST_DO {
        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 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);

    // 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);
    CX_TEST_DO {
        CX_TEST_CALL_SUBROUTINE(test_mempool_realloc_verify, pool, CX_MEMPOOL_TYPE_SIMPLE);
    }
    cxMempoolFree(pool);
}

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);
    CX_TEST_DO {
        CX_TEST_CALL_SUBROUTINE(test_mempool_free_verify, pool);
    }
    cxMempoolFree(pool);
}

CX_TEST(test_mempool_free2) {
    CxMempool *pool = cxMempoolCreateAdvanced(4);
    CX_TEST_DO {
        CX_TEST_CALL_SUBROUTINE(test_mempool_free_verify, pool);
    }
    cxMempoolFree(pool);
}

CX_TEST(test_mempool_destroy) {
    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);
        test_mempool_destructor_called = 0;
        cxFree(pool->allocator, data);
        CX_TEST_ASSERT(test_mempool_destructor_called == 1);
        data = cxMalloc(pool->allocator, sizeof(int));
        cxMempoolSetDestructor(data, test_mempool_destructor);
        cxMempoolFree(pool);
        CX_TEST_ASSERT(test_mempool_destructor_called == 2);
    }
}

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;
        cxMempoolRegister2(pool, &donotfree, test_mempool_destructor2, &test_mempool_destructor_called);
        cxMempoolFree(pool);
        CX_TEST_ASSERT(test_mempool_destructor_called == 2);
    }
}

CX_TEST(test_mempool_transfer) {
    CxMempool *src = cxMempoolCreateSimple(4);
    CxMempool *dest = cxMempoolCreateSimple(4);
    CX_TEST_DO {
        // allocate the first object
        int *c = cxMalloc(src->allocator, sizeof(int));
        // allocate the second object
        c = cxMalloc(src->allocator, sizeof(int));
        // check that the destructor functions are also transferred
        cxMempoolSetDestructor(c, test_mempool_destructor);
        // register foreign object
        c = malloc(sizeof(int));
        cxMempoolRegister(src, c, test_mempool_destructor);

        // check source pool
        CX_TEST_ASSERT(src->size == 2);
        CX_TEST_ASSERT(src->registered_size == 1);
        const CxAllocator *old_allocator = src->allocator;
        CX_TEST_ASSERT(old_allocator->data == src);

        // perform transfer
        int result = cxMempoolTransfer(src, dest);
        CX_TEST_ASSERT(result == 0);

        // check transfer
        CX_TEST_ASSERT(src->size == 0);
        CX_TEST_ASSERT(dest->size == 2);
        CX_TEST_ASSERT(src->registered_size == 0);
        CX_TEST_ASSERT(dest->registered_size == 2); // 1 object + old allocator
        CX_TEST_ASSERT(src->allocator != old_allocator);
        CX_TEST_ASSERT(old_allocator->data == dest);

        // verify that destroying old pool does nothing
        test_mempool_destructor_called = 0;
        cxMempoolFree(src);
        CX_TEST_ASSERT(test_mempool_destructor_called == 0);

        // cover illegal arguments
        result = cxMempoolTransfer(dest, dest);
        CX_TEST_ASSERT(result != 0);

        // verify that destroying new pool calls the destructors
        // but only two times (the old allocator has a different destructor)
        cxMempoolFree(dest);
        CX_TEST_ASSERT(test_mempool_destructor_called == 2);

        // free the foreign object
        free(c);
    }
}

CX_TEST(test_mempool_transfer_object) {
    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));
        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 == 2);

        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);
        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);

        // 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 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);

    return suite;
}

mercurial