Sat, 16 Apr 2022 20:17:01 +0200
improve testing allocator + add tests for it
test/CMakeLists.txt | file | annotate | diff | comparison | revisions | |
test/test_list.cpp | file | annotate | diff | comparison | revisions | |
test/util_allocator.c | file | annotate | diff | comparison | revisions | |
test/util_allocator.cpp | file | annotate | diff | comparison | revisions | |
test/util_allocator.h | file | annotate | diff | comparison | revisions |
--- a/test/CMakeLists.txt Sat Apr 16 18:02:10 2022 +0200 +++ b/test/CMakeLists.txt Sat Apr 16 20:17:01 2022 +0200 @@ -18,7 +18,7 @@ test_list.cpp test_tree.cpp selftest.cpp - util_allocator.c + util_allocator.cpp ) target_link_libraries(ucxtest PRIVATE ucx_static gtest_main) gtest_discover_tests(ucxtest)
--- a/test/test_list.cpp Sat Apr 16 18:02:10 2022 +0200 +++ b/test/test_list.cpp Sat Apr 16 20:17:01 2022 +0200 @@ -552,13 +552,11 @@ class HighLevelTest : public ::testing::Test { mutable std::unordered_set<CxList *> lists; protected: - void SetUp() override { - cxTestingAllocatorReset(); - } + CxTestingAllocator testingAllocator; void TearDown() override { for (auto &&l: lists) cxListDestroy(l); - EXPECT_TRUE(cxTestingAllocatorVerify()); + EXPECT_TRUE(testingAllocator.verify()); } static constexpr size_t testdata_len = 250; @@ -572,7 +570,7 @@ auto linkedListFromTestData() const -> CxList * { return autofree( cxLinkedListFromArray( - cxTestingAllocator, + &testingAllocator, cmp_int, sizeof(int), testdata_len, @@ -582,15 +580,15 @@ } auto pointerLinkedListFromTestData() const -> CxList * { - auto list = autofree(cxPointerLinkedListCreate(cxTestingAllocator, cmp_int)); + auto list = autofree(cxPointerLinkedListCreate(&testingAllocator, cmp_int)); cx_for_n(i, testdata_len) cxListAdd(list, &testdata.data[i]); return list; } - static void verifyCreate(CxList *list) { + void verifyCreate(CxList *list) const { EXPECT_EQ(list->size, 0); EXPECT_EQ(list->capacity, (size_t) -1); - EXPECT_EQ(list->allocator, cxTestingAllocator); + EXPECT_EQ(list->allocator, &testingAllocator); EXPECT_EQ(list->cmpfunc, cmp_int); } @@ -736,41 +734,41 @@ }; TEST_F(LinkedList, cxLinkedListCreate) { - CxList *list = autofree(cxLinkedListCreate(cxTestingAllocator, cmp_int, sizeof(int))); + CxList *list = autofree(cxLinkedListCreate(&testingAllocator, cmp_int, sizeof(int))); EXPECT_EQ(list->itemsize, sizeof(int)); verifyCreate(list); } TEST_F(PointerLinkedList, cxPointerLinkedListCreate) { - CxList *list = autofree(cxPointerLinkedListCreate(cxTestingAllocator, cmp_int)); + CxList *list = autofree(cxPointerLinkedListCreate(&testingAllocator, cmp_int)); EXPECT_EQ(list->itemsize, sizeof(void *)); verifyCreate(list); } TEST_F(LinkedList, cxLinkedListFromArray) { - CxList *expected = autofree(cxLinkedListCreate(cxTestingAllocator, cmp_int, sizeof(int))); + CxList *expected = autofree(cxLinkedListCreate(&testingAllocator, cmp_int, sizeof(int))); cx_for_n (i, testdata_len) cxListAdd(expected, &testdata.data[i]); - CxList *list = autofree(cxLinkedListFromArray(cxTestingAllocator, cmp_int, sizeof(int), + CxList *list = autofree(cxLinkedListFromArray(&testingAllocator, cmp_int, sizeof(int), testdata_len, testdata.data.data())); EXPECT_EQ(cxListCompare(list, expected), 0); } TEST_F(LinkedList, cxListAdd) { - CxList *list = autofree(cxLinkedListCreate(cxTestingAllocator, cmp_int, sizeof(int))); + CxList *list = autofree(cxLinkedListCreate(&testingAllocator, cmp_int, sizeof(int))); verifyAdd(list, false); } TEST_F(PointerLinkedList, cxListAdd) { - CxList *list = autofree(cxPointerLinkedListCreate(cxTestingAllocator, cmp_int)); + CxList *list = autofree(cxPointerLinkedListCreate(&testingAllocator, cmp_int)); verifyAdd(list, true); } TEST_F(LinkedList, cxListInsert) { - verifyInsert(autofree(cxLinkedListCreate(cxTestingAllocator, cmp_int, sizeof(int)))); + verifyInsert(autofree(cxLinkedListCreate(&testingAllocator, cmp_int, sizeof(int)))); } TEST_F(PointerLinkedList, cxListInsert) { - verifyInsert(autofree(cxPointerLinkedListCreate(cxTestingAllocator, cmp_int))); + verifyInsert(autofree(cxPointerLinkedListCreate(&testingAllocator, cmp_int))); } TEST_F(LinkedList, cxListRemove) { @@ -815,13 +813,13 @@ TEST_F(LinkedList, InsertViaIterator) { int fivenums[] = {0, 1, 2, 3, 4, 5}; - CxList *list = autofree(cxLinkedListFromArray(cxTestingAllocator, cmp_int, sizeof(int), 5, fivenums)); + CxList *list = autofree(cxLinkedListFromArray(&testingAllocator, cmp_int, sizeof(int), 5, fivenums)); verifyInsertViaIterator(list); } TEST_F(PointerLinkedList, InsertViaIterator) { int fivenums[] = {0, 1, 2, 3, 4, 5}; - CxList *list = autofree(cxPointerLinkedListCreate(cxTestingAllocator, cmp_int)); + CxList *list = autofree(cxPointerLinkedListCreate(&testingAllocator, cmp_int)); cx_for_n (i, 5) cxListAdd(list, &fivenums[i]); verifyInsertViaIterator(list); }
--- a/test/util_allocator.c Sat Apr 16 18:02:10 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +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" -#include <string.h> - -void cx_testing_allocator_add(cx_testing_allocator_s *data, void *ptr) { - data->tracked[data->live] = ptr; - data->live++; -} - -int cx_testing_allocator_remove(cx_testing_allocator_s *data, void *ptr) { - for (int i = 0; i < data->live; i++) { - if (data->tracked[i] == ptr) { - data->tracked[i] = data->tracked[data->live - 1]; - data->live--; - return 0; - } - } - return 1; -} - -void *cx_malloc_testing(void *d, size_t n) { - cx_testing_allocator_s *data = d; - void *ptr = malloc(n); - data->alloc_total++; - if (ptr == NULL) { - data->alloc_failed++; - } else { - cx_testing_allocator_add(data, ptr); - } - return ptr; -} - -void *cx_realloc_testing(void *d, void *mem, size_t n) { - cx_testing_allocator_s *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_remove(data, mem)) { - data->free_failed++; - } - cx_testing_allocator_add(data, ptr); - } - return ptr; - } -} - -void *cx_calloc_testing(void *d, size_t nelem, size_t n) { - cx_testing_allocator_s *data = d; - void *ptr = calloc(nelem, n); - data->alloc_total++; - if (ptr == NULL) { - data->alloc_failed++; - } else { - cx_testing_allocator_add(data, ptr); - } - return ptr; -} - -void cx_free_testing(void *d, void *mem) { - cx_testing_allocator_s *data = d; - data->free_total++; - if (cx_testing_allocator_remove(data, mem)) { - 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 -}; - -cx_testing_allocator_s cx_testing_allocator_data; - -struct cx_allocator_s cx_testing_allocator = { - &cx_testing_allocator_class, - &cx_testing_allocator_data -}; -CxAllocator *cxTestingAllocator = &cx_testing_allocator; - -void cxTestingAllocatorReset(void) { - memset(&cx_testing_allocator_data, 0, sizeof(cx_testing_allocator_s)); -} - -bool cxTestingAllocatorVerify(void) { - return cx_testing_allocator_data.live == 0 - && cx_testing_allocator_data.alloc_failed == 0 && cx_testing_allocator_data.free_failed == 0 - && cx_testing_allocator_data.alloc_total == cx_testing_allocator_data.free_total; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/util_allocator.cpp Sat Apr 16 20:17:01 2022 +0200 @@ -0,0 +1,161 @@ +/* + * 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::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()); + auto ptr = cxMalloc(&allocator, 16); + 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/test/util_allocator.h Sat Apr 16 18:02:10 2022 +0200 +++ b/test/util_allocator.h Sat Apr 16 20:17:01 2022 +0200 @@ -31,60 +31,44 @@ #include "cx/allocator.h" -#ifdef __cplusplus -extern "C" { -#endif +#include <set> -#define CX_TESTING_ALLOCATOR_MAX_LIVE 1024 - -typedef struct { +struct CxTestingAllocator : public CxAllocator { /** * Total number of all allocations (malloc, calloc, realloc). * A realloc() does only count when the memory has to be moved. */ - int alloc_total; + unsigned alloc_total = 0; /** * Number of failed allocations (malloc, calloc, realloc). */ - int alloc_failed; + unsigned alloc_failed = 0; /** * Total number of freed pointers. * A reallocation also counts as a free when the memory has to be moved. */ - int free_total; + unsigned free_total = 0; /** * Number of failed free invocations. * A free() is considered failed, if it has not been performed on tracked memory. */ - int free_failed; - /** - * Number of memory blocks that are currently live (and tracked). - * The maximum number of tracked blocks is defined in #CX_TESTING_ALLOCATOR_MAX_LIVE. - */ - int live; + unsigned free_failed = 0; /** - * The array of tracked memory blocks. + * The set of tracked memory blocks. */ - void *tracked[CX_TESTING_ALLOCATOR_MAX_LIVE]; -} cx_testing_allocator_s; - -extern CxAllocator *cxTestingAllocator; + std::set<void *> tracked; -/** - * Resets the testing allocator information. - * This function SHOULD be called prior to any use of this allocator. - */ -void cxTestingAllocatorReset(void); + /** + * Constructs a new testing allocator. + */ + CxTestingAllocator(); -/** - * Checks whether all allocated memory is properly freed and no failed (de)allocations happened. - * - * @return true on success, false if there was any problem - */ -bool cxTestingAllocatorVerify(void); - -#ifdef __cplusplus -}; /* extern "C" */ -#endif + /** + * Verifies that all allocated memory blocks are freed and no free occurred twice. + * + * @return true iff all tracked allocations / deallocations were valid + */ + bool verify() const; +}; #endif /* UCX_UTIL_ALLOCATOR_H */