add test coverage for allocation failures in clone-function default tip

Fri, 24 Oct 2025 21:15:14 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 24 Oct 2025 21:15:14 +0200
changeset 1443
6986e9ed70f3
parent 1442
244fb8bc3584

add test coverage for allocation failures in clone-function

tests/test_hash_map.c file | annotate | diff | comparison | revisions
tests/test_list.c file | annotate | diff | comparison | revisions
--- a/tests/test_hash_map.c	Fri Oct 24 20:52:10 2025 +0200
+++ b/tests/test_hash_map.c	Fri Oct 24 21:15:14 2025 +0200
@@ -513,8 +513,11 @@
     cx_testing_allocator_destroy(&talloc);
 }
 
+static unsigned test_hash_map_clone_func_max_clones;
 static void *test_hash_map_clone_func(void *dst, const void *src,
         const CxAllocator *al, void *data) {
+    if (test_hash_map_clone_func_max_clones == 0) return NULL;
+    test_hash_map_clone_func_max_clones--;
     if (dst == NULL) {
         dst = cxMalloc(al, sizeof(int));
     }
@@ -535,6 +538,7 @@
     }
     CX_TEST_DO {
         int c = 4;
+        test_hash_map_clone_func_max_clones = 100; // no limit
         size_t cloned = cxMapClone(dst, src, test_hash_map_clone_func, NULL, &c);
         CX_TEST_ASSERT(cloned == 3);
         CX_TEST_ASSERT(cxMapSize(dst) == 5);
@@ -552,6 +556,38 @@
     cxMapFree(src);
 }
 
+CX_TEST(test_hash_map_clone_alloc_fail) {
+    CxMap *dst = cxHashMapCreateSimple(sizeof(int));
+    CxMap *src = cxHashMapCreateSimple(sizeof(int));
+    const char *exist_keys[] = {"k1", "k2", "k3"};
+    int exists[] = {1, 3, 4};
+    const char *source_keys[] = {"k4", "k2", "k5"};
+    int source[] = {7, 9, 15};
+    for (unsigned int i = 0 ; i < 3 ; i++) {
+        cxMapPut(dst, exist_keys[i], &exists[i]);
+        cxMapPut(src, source_keys[i], &source[i]);
+    }
+    CX_TEST_DO {
+        int c = 4;
+        test_hash_map_clone_func_max_clones = 2;
+        size_t cloned = cxMapClone(dst, src, test_hash_map_clone_func, NULL, &c);
+        CX_TEST_ASSERT(cloned == 2);
+        CX_TEST_ASSERT(cxMapSize(dst) == 4);
+        CX_TEST_ASSERT(*((int*)cxMapGet(dst, "k1")) == 1);
+        CX_TEST_ASSERT(*((int*)cxMapGet(dst, "k2")) == 13);
+        CX_TEST_ASSERT(*((int*)cxMapGet(dst, "k3")) == 4);
+        // the concrete element which is affected might change when the hash function changes
+        CX_TEST_ASSERT(cxMapGet(dst, "k4") == NULL);
+        CX_TEST_ASSERT(*((int*)cxMapGet(dst, "k5")) == 19);
+        CX_TEST_ASSERT(cxMapSize(src) == 3);
+        CX_TEST_ASSERT(*((int*)cxMapGet(src, "k4")) == 7);
+        CX_TEST_ASSERT(*((int*)cxMapGet(src, "k2")) == 9);
+        CX_TEST_ASSERT(*((int*)cxMapGet(src, "k5")) == 15);
+    }
+    cxMapFree(dst);
+    cxMapFree(src);
+}
+
 CX_TEST(test_hash_map_clone_ptr) {
     CxTestingAllocator talloc;
     cx_testing_allocator_init(&talloc);
@@ -571,6 +607,7 @@
     }
     CX_TEST_DO {
         int c = 4;
+        test_hash_map_clone_func_max_clones = 100; // no limit
         size_t cloned = cxMapClone(dst, src, test_hash_map_clone_func, allocator, &c);
         CX_TEST_ASSERT(cloned == 3);
         CX_TEST_ASSERT(cxMapSize(dst) == 5);
@@ -938,6 +975,7 @@
     cx_test_register(suite, test_hash_map_simple_destructor_pointers);
     cx_test_register(suite, test_hash_map_advanced_destructor_pointers);
     cx_test_register(suite, test_hash_map_clone);
+    cx_test_register(suite, test_hash_map_clone_alloc_fail);
     cx_test_register(suite, test_hash_map_clone_ptr);
     cx_test_register(suite, test_empty_map_no_ops);
     cx_test_register(suite, test_empty_map_size);
--- a/tests/test_list.c	Fri Oct 24 20:52:10 2025 +0200
+++ b/tests/test_list.c	Fri Oct 24 21:15:14 2025 +0200
@@ -2526,7 +2526,11 @@
     free(testdata);
 })
 
+static unsigned test_clone_func_max_clones;
 static void *test_clone_func(void *dest, const void *src, const CxAllocator *al, void *data) {
+    if (test_clone_func_max_clones == 0) return NULL;
+    test_clone_func_max_clones--;
+
     if (dest == NULL) {
         dest = cxMalloc(al, sizeof(int));
     }
@@ -2534,7 +2538,6 @@
     *((int*) dest) = *(int*) src + *(int*) data;
     (*(int*) data)++;
 
-    // TODO: create a separate test with another clone func which simulates a failure after a few allocations
     return dest;
 }
 
@@ -2570,6 +2573,7 @@
     // perform the test
     int expected_data[12] = array_init(1, 2, 3, 4, 1, 3, 5, 7, 9, 11, 13, 15);
     int c = 0;
+    test_clone_func_max_clones = 100; // no limit
     size_t cloned = cxListClone(target, source, test_clone_func, testing_alloc, &c);
     CX_TEST_ASSERT(cloned == 8);
     CX_TEST_ASSERT(c == 8);
@@ -2586,6 +2590,55 @@
     cx_testing_allocator_destroy(&talloc);
 }
 
+static CX_TEST_SUBROUTINE(verify_clone_alloc_fail, CxList *target, CxList *source, bool target_isptrlist) {
+    // testing allocator for the target elements if target_isptrlist is true
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *testing_alloc = &talloc.base;
+
+    // register a destructor for the target list if it is storing pointers
+    if (target_isptrlist) {
+        cxDefineAdvancedDestructor(target, cxFree, testing_alloc);
+    }
+
+    // fill the source list
+    int source_data[8] = array_init(1, 2, 3, 4, 5, 6, 7, 8);
+    for (unsigned i = 0 ; i < 8 ; i++) {
+        cxListAdd(source, &source_data[i]);
+    }
+
+    // add some initial data to the target
+    int initial_data[4] = array_init(1, 2, 3, 4);
+    for (unsigned i = 0; i < 4; i++) {
+        if (target_isptrlist) {
+            int *x = cxMalloc(testing_alloc, sizeof(int));
+            *x = initial_data[i];
+            cxListAdd(target, x);
+        } else {
+            cxListAdd(target, &initial_data[i]);
+        }
+    }
+
+    // perform the test
+    int expected_data[9] = array_init(1, 2, 3, 4, 1, 3, 5, 7, 9);
+    int c = 0;
+    test_clone_func_max_clones = 5;
+    size_t cloned = cxListClone(target, source, test_clone_func, testing_alloc, &c);
+    CX_TEST_ASSERT(cloned == 5);
+    CX_TEST_ASSERT(c == 5);
+    CX_TEST_ASSERT(cxListSize(target) == 9);
+    CX_TEST_ASSERT(cxListSize(source) == 8);
+    for (unsigned i = 0 ; i < 9 ; i++) {
+        CX_TEST_ASSERT(*(int*)cxListAt(target, i) == expected_data[i]);
+    }
+    for (unsigned i = 0 ; i < 8 ; i++) {
+        CX_TEST_ASSERT(*(int*)cxListAt(source, i) == source_data[i]);
+    }
+    cxListFree(target);
+    CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    cx_testing_allocator_destroy(&talloc);
+}
+
 roll_out_test_combos(clone_into_arl, {
     CxList *target = cxArrayListCreate(cxDefaultAllocator, cx_cmp_int, sizeof(int), 8);
     CX_TEST_CALL_SUBROUTINE(verify_clone, target, list, false);
@@ -2606,6 +2659,26 @@
     CX_TEST_CALL_SUBROUTINE(verify_clone, target, list, true);
 })
 
+roll_out_test_combos(clone_alloc_fail_into_arl, {
+    CxList *target = cxArrayListCreate(cxDefaultAllocator, cx_cmp_int, sizeof(int), 8);
+    CX_TEST_CALL_SUBROUTINE(verify_clone_alloc_fail, target, list, false);
+})
+
+roll_out_test_combos(clone_alloc_fail_into_ll, {
+    CxList *target = cxLinkedListCreate(cxDefaultAllocator, cx_cmp_int, sizeof(int));
+    CX_TEST_CALL_SUBROUTINE(verify_clone_alloc_fail, target, list, false);
+})
+
+roll_out_test_combos(clone_alloc_fail_into_parl, {
+    CxList *target = cxArrayListCreate(cxDefaultAllocator, cx_cmp_int, CX_STORE_POINTERS, 8);
+    CX_TEST_CALL_SUBROUTINE(verify_clone_alloc_fail, target, list, true);
+})
+
+roll_out_test_combos(clone_alloc_fail_into_pll, {
+    CxList *target = cxLinkedListCreate(cxDefaultAllocator, cx_cmp_int, CX_STORE_POINTERS);
+    CX_TEST_CALL_SUBROUTINE(verify_clone_alloc_fail, target, list, true);
+})
+
 CX_TEST(test_list_pointer_list_supports_null) {
     CxList *list = cxLinkedListCreate(cxDefaultAllocator, cx_cmp_int, CX_STORE_POINTERS);
     int x = 47;
@@ -2786,6 +2859,14 @@
     cx_test_register(suite, test_list_parl_clone_into_parl);
     cx_test_register(suite, test_list_arl_clone_into_pll);
     cx_test_register(suite, test_list_parl_clone_into_pll);
+    cx_test_register(suite, test_list_arl_clone_alloc_fail_into_arl);
+    cx_test_register(suite, test_list_parl_clone_alloc_fail_into_arl);
+    cx_test_register(suite, test_list_arl_clone_alloc_fail_into_ll);
+    cx_test_register(suite, test_list_parl_clone_alloc_fail_into_ll);
+    cx_test_register(suite, test_list_arl_clone_alloc_fail_into_parl);
+    cx_test_register(suite, test_list_parl_clone_alloc_fail_into_parl);
+    cx_test_register(suite, test_list_arl_clone_alloc_fail_into_pll);
+    cx_test_register(suite, test_list_parl_clone_alloc_fail_into_pll);
 
     return suite;
 }
@@ -2913,6 +2994,14 @@
     cx_test_register(suite, test_list_pll_clone_into_parl);
     cx_test_register(suite, test_list_ll_clone_into_pll);
     cx_test_register(suite, test_list_pll_clone_into_pll);
+    cx_test_register(suite, test_list_ll_clone_alloc_fail_into_arl);
+    cx_test_register(suite, test_list_pll_clone_alloc_fail_into_arl);
+    cx_test_register(suite, test_list_ll_clone_alloc_fail_into_ll);
+    cx_test_register(suite, test_list_pll_clone_alloc_fail_into_ll);
+    cx_test_register(suite, test_list_ll_clone_alloc_fail_into_parl);
+    cx_test_register(suite, test_list_pll_clone_alloc_fail_into_parl);
+    cx_test_register(suite, test_list_ll_clone_alloc_fail_into_pll);
+    cx_test_register(suite, test_list_pll_clone_alloc_fail_into_pll);
 
     return suite;
 }
@@ -3014,6 +3103,14 @@
     cx_test_register(suite, test_list_pkvl_clone_into_parl);
     cx_test_register(suite, test_list_kvl_clone_into_pll);
     cx_test_register(suite, test_list_pkvl_clone_into_pll);
+    cx_test_register(suite, test_list_kvl_clone_alloc_fail_into_arl);
+    cx_test_register(suite, test_list_pkvl_clone_alloc_fail_into_arl);
+    cx_test_register(suite, test_list_kvl_clone_alloc_fail_into_ll);
+    cx_test_register(suite, test_list_pkvl_clone_alloc_fail_into_ll);
+    cx_test_register(suite, test_list_kvl_clone_alloc_fail_into_parl);
+    cx_test_register(suite, test_list_pkvl_clone_alloc_fail_into_parl);
+    cx_test_register(suite, test_list_kvl_clone_alloc_fail_into_pll);
+    cx_test_register(suite, test_list_pkvl_clone_alloc_fail_into_pll);
 
     return suite;
 }

mercurial