Wed, 27 Dec 2023 16:04:38 +0100
migrates self-test for testing allocator - relates to #342
CHANGELOG | file | annotate | diff | comparison | revisions | |
tests/Makefile | file | annotate | diff | comparison | revisions | |
tests/ucxtest.c | file | annotate | diff | comparison | revisions | |
tests/util_allocator.c | file | annotate | diff | comparison | revisions | |
tests/util_allocator.cpp | file | annotate | diff | comparison | revisions | |
tests/util_allocator.h | file | annotate | diff | comparison | revisions |
--- a/CHANGELOG Wed Dec 27 14:54:04 2023 +0100 +++ b/CHANGELOG Wed Dec 27 16:04:38 2023 +0100 @@ -4,8 +4,10 @@ * adds cxListFindRemove() * adds cxBufferReset() * adds cx_cmp_ptr() + * adds improved version of UCX 2 Test framework (now a self-contained header) * fixes wrong link from UCX 2 documentation to UCX 3 documentation - * removes CMake, except for tests at the moment, in favor of uwproj + * removes CMake + * removes GTest dependency Version 3.0 - 2023-07-09 ------------------------
--- a/tests/Makefile Wed Dec 27 14:54:04 2023 +0100 +++ b/tests/Makefile Wed Dec 27 16:04:38 2023 +0100 @@ -27,7 +27,7 @@ TEST_DIR=$(build_dir)/tests -SRC = test_utils.c test_hash_key.c ucxtest.c +SRC = util_allocator.c test_utils.c test_hash_key.c ucxtest.c OBJ_EXT=.o OBJ=$(SRC:%.c=$(TEST_DIR)/%$(OBJ_EXT)) @@ -69,3 +69,8 @@ @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< +$(TEST_DIR)/util_allocator$(OBJ_EXT): util_allocator.c util_allocator.h \ + ../src/cx/allocator.h ../src/cx/common.h ../src/cx/test.h + @echo "Compiling $<" + $(CC) -o $@ $(CFLAGS) -c $< +
--- a/tests/ucxtest.c Wed Dec 27 14:54:04 2023 +0100 +++ b/tests/ucxtest.c Wed Dec 27 16:04:38 2023 +0100 @@ -28,32 +28,27 @@ #include "cx/test.h" +CxTestSuite *cx_test_suite_testing_allocator(void); CxTestSuite *cx_test_suite_utils(void); CxTestSuite *cx_test_suite_hash_key(void); #define run_tests(suite) cx_test_run_stdout(suite); success += (suite)->success; failure += (suite)->failure +#define execute_test_suites(...) unsigned success = 0, failure = 0; CxTestSuite* test_suites[] = {__VA_ARGS__}; \ + for (size_t i = 0; i < sizeof(test_suites)/sizeof(void*) ; i++) {run_tests(test_suites[i]);} (void)0 +#define free_test_suites for (size_t i = 0 ; i < sizeof(test_suites)/sizeof(void*) ; i++) {cx_test_suite_free(test_suites[i]);} (void)0 int main(void) { printf("UCX Tests\n---------\n"); - unsigned success = 0, failure = 0; - - // create test suites - CxTestSuite - *utils = cx_test_suite_utils(), - *hash_key = cx_test_suite_hash_key(); - - // run tests - run_tests(utils); - run_tests(hash_key); - - // print overall result + execute_test_suites( + cx_test_suite_testing_allocator(), + cx_test_suite_utils(), + cx_test_suite_hash_key() + ); printf("=== OVERALL RESULT ===\n"); printf(" Total: %u\n Success: %u\n Failure: %u\n", success + failure, success, failure); - - cx_test_suite_free(utils); - cx_test_suite_free(hash_key); + free_test_suites; return failure > 0 ? 1 : 0; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/util_allocator.c Wed Dec 27 16:04:38 2023 +0100 @@ -0,0 +1,253 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 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 "util_allocator.h" +#include "cx/test.h" + +static void cx_testing_allocator_track(CxTestingAllocator *alloc, void *ptr) { + for (size_t i = 0; i < alloc->tracked_count; i++) { + if (alloc->tracked[i] == ptr) return; // is already tracked + } + + if (alloc->tracked_count == alloc->tracked_capacity) { + size_t newcapa = alloc->tracked_capacity + 64; + void *newarr = realloc(alloc->tracked, newcapa * sizeof(void *)); + if (newarr == NULL) abort(); + alloc->tracked = newarr; + alloc->tracked_capacity = newcapa; + } + + alloc->tracked[alloc->tracked_count] = ptr; + alloc->tracked_count++; +} + +static bool cx_testing_allocator_untrack(CxTestingAllocator *alloc, void *ptr) { + for (size_t i = 0; i < alloc->tracked_count; i++) { + if (alloc->tracked[i] == ptr) { + size_t last = alloc->tracked_count - 1; + if (i < last) { + alloc->tracked[i] = alloc->tracked[last]; + } + alloc->tracked_count--; + return true; + } + } + return false; +} + +static void *cx_malloc_testing(void *d, size_t n) { + CxTestingAllocator *data = d; + void *ptr = malloc(n); + data->alloc_total++; + if (ptr == NULL) { + data->alloc_failed++; + } else { + cx_testing_allocator_track(data, ptr); + } + return ptr; +} + +static void *cx_realloc_testing(void *d, void *mem, size_t n) { + CxTestingAllocator *data = d; + void *ptr = realloc(mem, n); + if (ptr == mem) { + return ptr; + } else { + data->alloc_total++; + if (ptr == NULL) { + data->alloc_failed++; + } else { + data->free_total++; + if (!cx_testing_allocator_untrack(data, mem)) { + data->free_failed++; + } + cx_testing_allocator_track(data, ptr); + } + return ptr; + } +} + +static void *cx_calloc_testing(void *d, size_t nelem, size_t n) { + CxTestingAllocator *data = d; + void *ptr = calloc(nelem, n); + data->alloc_total++; + if (ptr == NULL) { + data->alloc_failed++; + } else { + cx_testing_allocator_track(data, ptr); + } + return ptr; +} + +static void cx_free_testing(void *d, void *mem) { + CxTestingAllocator *data = d; + data->free_total++; + if (cx_testing_allocator_untrack(data, mem)) { + free(mem); + } else { + data->free_failed++; + // do not even attempt to free mem, because it is likely to segfault + } +} + +cx_allocator_class cx_testing_allocator_class = { + cx_malloc_testing, + cx_realloc_testing, + cx_calloc_testing, + cx_free_testing +}; + + +void cx_testing_allocator_init(CxTestingAllocator *alloc) { + alloc->base.cl = &cx_testing_allocator_class; + alloc->base.data = alloc; + alloc->alloc_failed = 0; + alloc->alloc_total = 0; + alloc->free_failed = 0; + alloc->free_total = 0; + size_t initial_capa = 16; + alloc->tracked_capacity = initial_capa; + alloc->tracked_count = 0; + alloc->tracked = calloc(sizeof(void *), initial_capa); +} + +void cx_testing_allocator_destroy(CxTestingAllocator *alloc) { + free(alloc->tracked); +} + +bool cx_testing_allocator_used(CxTestingAllocator const *alloc) { + return alloc->alloc_total > 0; +} + +bool cx_testing_allocator_verify(CxTestingAllocator const *alloc) { + return alloc->tracked_count == 0 && alloc->alloc_failed == 0 && alloc->free_failed == 0 + && alloc->alloc_total == alloc->free_total; +} + +// SELF-TEST + +CX_TEST(test_util_allocator_expect_free) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + CxAllocator *alloc = &talloc.base; + CX_TEST_DO { + CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc), + "Fresh testing allocator fails to verify."); + CX_TEST_ASSERTM(!cx_testing_allocator_used(&talloc), + "Fresh testing allocator already used."); + void *ptr = cxMalloc(alloc, 16); + CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc), + "Testing allocator verifies with unfreed memory."); + CX_TEST_ASSERT(cx_testing_allocator_used(&talloc)); + CX_TEST_ASSERT(ptr != NULL); + + cxFree(alloc, ptr); + CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc), + "Testing allocator fails to verify after everything freed."); + } + cx_testing_allocator_destroy(&talloc); +} + +CX_TEST(test_util_allocator_detect_double_free) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + CxAllocator *alloc = &talloc.base; + CX_TEST_DO { + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + void *ptr = cxMalloc(alloc, 16); + CX_TEST_ASSERT(ptr != NULL); + cxFree(alloc, ptr); + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + cxFree(alloc, ptr); + CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc), + "Testing allocator does not detect double-free."); + } + cx_testing_allocator_destroy(&talloc); +} + +CX_TEST(test_util_allocator_free_untracked) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + CxAllocator *alloc = &talloc.base; + void *ptr = malloc(16); + CX_TEST_DO { + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + cxFree(alloc, ptr); + CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc), + "Testing allocator does not detect free of untracked memory."); + } + free(ptr); + cx_testing_allocator_destroy(&talloc); +} + +CX_TEST(test_util_allocator_full_lifecycle_with_realloc) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + CxAllocator *alloc = &talloc.base; + CX_TEST_DO { + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + void *ptr = cxMalloc(alloc, 16); + CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); + CX_TEST_ASSERT(ptr != NULL); + CX_TEST_ASSERT(talloc.tracked_count == 1); + ptr = cxRealloc(alloc, ptr, 256); + CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); + CX_TEST_ASSERT(ptr != NULL); + CX_TEST_ASSERT(talloc.tracked_count == 1); + cxFree(alloc, ptr); + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + } + cx_testing_allocator_destroy(&talloc); +} + +CX_TEST(test_util_allocator_calloc_initializes) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + CxAllocator *alloc = &talloc.base; + CX_TEST_DO { + const char zeros[16] = {0}; + void *ptr = cxCalloc(alloc, 16, 1); + CX_TEST_ASSERT(memcmp(ptr, zeros, 16) == 0); + cxFree(alloc, ptr); + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + } + cx_testing_allocator_destroy(&talloc); +} + +CxTestSuite *cx_test_suite_testing_allocator(void) { + CxTestSuite *suite = cx_test_suite_new("testing allocator self-test"); + + cx_test_register(suite, test_util_allocator_expect_free); + cx_test_register(suite, test_util_allocator_detect_double_free); + cx_test_register(suite, test_util_allocator_free_untracked); + cx_test_register(suite, test_util_allocator_full_lifecycle_with_realloc); + cx_test_register(suite, test_util_allocator_calloc_initializes); + + return suite; +} +
--- a/tests/util_allocator.cpp Wed Dec 27 14:54:04 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,167 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2021 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 "util_allocator.h" - -void *cx_malloc_testing(void *d, size_t n) { - auto data = reinterpret_cast<CxTestingAllocator *>(d); - void *ptr = malloc(n); - data->alloc_total++; - if (ptr == nullptr) { - data->alloc_failed++; - } else { - data->tracked.insert(ptr); - } - return ptr; -} - -void *cx_realloc_testing(void *d, void *mem, size_t n) { - auto data = reinterpret_cast<CxTestingAllocator *>(d); - void *ptr = realloc(mem, n); - if (ptr == mem) { - return ptr; - } else { - data->alloc_total++; - if (ptr == nullptr) { - data->alloc_failed++; - } else { - data->free_total++; - if (data->tracked.erase(mem) == 0) { - data->free_failed++; - } - data->tracked.insert(ptr); - } - return ptr; - } -} - -void *cx_calloc_testing(void *d, size_t nelem, size_t n) { - auto data = reinterpret_cast<CxTestingAllocator *>(d); - void *ptr = calloc(nelem, n); - data->alloc_total++; - if (ptr == nullptr) { - data->alloc_failed++; - } else { - data->tracked.insert(ptr); - } - return ptr; -} - -void cx_free_testing(void *d, void *mem) { - auto data = reinterpret_cast<CxTestingAllocator *>(d); - data->free_total++; - if (data->tracked.erase(mem) == 0) { - data->free_failed++; - // do not even attempt to free mem, because it is likely to segfault - } else { - free(mem); - } -} - -cx_allocator_class cx_testing_allocator_class = { - cx_malloc_testing, - cx_realloc_testing, - cx_calloc_testing, - cx_free_testing -}; - -CxTestingAllocator::CxTestingAllocator() : CxAllocator() { - cl = &cx_testing_allocator_class; - data = this; -} - -bool CxTestingAllocator::used() const { - return alloc_total > 0; -} - -bool CxTestingAllocator::verify() const { - return tracked.empty() && alloc_failed == 0 && free_failed == 0 && alloc_total == free_total; -} - -// SELF-TEST - -#include <gtest/gtest.h> - -TEST(TestingAllocator, ExpectFree) { - CxTestingAllocator allocator; - - ASSERT_TRUE(allocator.verify()); - EXPECT_FALSE(allocator.used()); - auto ptr = cxMalloc(&allocator, 16); - EXPECT_TRUE(allocator.used()); - ASSERT_NE(ptr, nullptr); - EXPECT_FALSE(allocator.verify()); - - cxFree(&allocator, ptr); - EXPECT_TRUE(allocator.verify()); -} - -TEST(TestingAllocator, DetectDoubleFree) { - CxTestingAllocator allocator; - - ASSERT_TRUE(allocator.verify()); - auto ptr = cxMalloc(&allocator, 16); - ASSERT_NE(ptr, nullptr); - - cxFree(&allocator, ptr); - EXPECT_TRUE(allocator.verify()); - ASSERT_NO_FATAL_FAILURE(cxFree(&allocator, ptr)); - EXPECT_FALSE(allocator.verify()); -} - -TEST(TestingAllocator, FreeUntracked) { - CxTestingAllocator allocator; - - auto ptr = malloc(16); - ASSERT_TRUE(allocator.verify()); - ASSERT_NO_FATAL_FAILURE(cxFree(&allocator, ptr)); - EXPECT_FALSE(allocator.verify()); - ASSERT_NO_FATAL_FAILURE(free(ptr)); -} - -TEST(TestingAllocator, FullLifecycleWithRealloc) { - CxTestingAllocator allocator; - ASSERT_TRUE(allocator.verify()); - auto ptr = cxMalloc(&allocator, 16); - ASSERT_NE(ptr, nullptr); - EXPECT_EQ(allocator.tracked.size(), 1); - ptr = cxRealloc(&allocator, ptr, 256); - ASSERT_NE(ptr, nullptr); - EXPECT_EQ(allocator.tracked.size(), 1); - cxFree(&allocator, ptr); - EXPECT_TRUE(allocator.verify()); -} - -TEST(TestingAllocator, CallocInitializes) { - CxTestingAllocator allocator; - const char zeros[16] = {0}; - auto ptr = cxCalloc(&allocator, 16, 1); - EXPECT_EQ(memcmp(ptr, zeros, 16), 0); - cxFree(&allocator, ptr); - EXPECT_TRUE(allocator.verify()); -}
--- a/tests/util_allocator.h Wed Dec 27 14:54:04 2023 +0100 +++ b/tests/util_allocator.h Wed Dec 27 16:04:38 2023 +0100 @@ -26,56 +26,77 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef UCX_UTIL_ALLOCATOR_H -#define UCX_UTIL_ALLOCATOR_H +#ifndef UCX_TEST_UTIL_ALLOCATOR_H +#define UCX_TEST_UTIL_ALLOCATOR_H #include "cx/allocator.h" -#include <set> +#ifdef __cplusplus +#extern "C" { +#endif -struct CxTestingAllocator : public CxAllocator { +typedef struct CxTestingAllocator { + CxAllocator base; /** * Total number of all allocations (malloc, calloc, realloc). * A realloc() does only count when the memory has to be moved. */ - unsigned alloc_total = 0; + unsigned alloc_total; /** * Number of failed allocations (malloc, calloc, realloc). */ - unsigned alloc_failed = 0; + unsigned alloc_failed; /** * Total number of freed pointers. * A reallocation also counts as a free when the memory has to be moved. */ - unsigned free_total = 0; + unsigned free_total; /** * Number of failed free invocations. * A free() is considered failed, if it has not been performed on tracked memory. */ - unsigned free_failed = 0; + unsigned free_failed; + /** + * The number of currently tracked memory blocks. + */ + size_t tracked_count; + /** + * The capaciyty of the \c tracked array. + */ + size_t tracked_capacity; /** * The set of tracked memory blocks. */ - std::set<void *> tracked; + void **tracked; +} CxTestingAllocator; + - /** - * Constructs a new testing allocator. - */ - CxTestingAllocator(); +/** + * Initializes a new testing allocator. + */ +void cx_testing_allocator_init(CxTestingAllocator *alloc); + +/** + * Destroys a testing allocator. + */ +void cx_testing_allocator_destroy(CxTestingAllocator *alloc); - /** - * Verifies that this allocator has been used. - * - * @return true if any allocation was attempted using this allocator - */ - [[nodiscard]] bool used() const; +/** + * Verifies that this allocator has been used. + * + * @return true if any allocation was attempted using this allocator + */ +bool cx_testing_allocator_used(CxTestingAllocator const *alloc); - /** - * Verifies that all allocated memory blocks are freed and no free occurred twice. - * - * @return true iff all tracked allocations / deallocations were valid - */ - [[nodiscard]] bool verify() const; -}; +/** + * Verifies that all allocated memory blocks are freed and no free occurred twice. + * + * @return true iff all tracked allocations / deallocations were valid + */ +bool cx_testing_allocator_verify(CxTestingAllocator const *alloc); -#endif // UCX_UTIL_ALLOCATOR_H +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_TEST_UTIL_ALLOCATOR_H