diff options
author | Markus Minichmayr <markus@tapkey.com> | 2018-03-05 16:59:07 +0100 |
---|---|---|
committer | Markus Minichmayr <markus@tapkey.com> | 2022-02-15 08:54:27 +0100 |
commit | f23716fdcb99fc95c0b82a8624bad38cb23bfa62 (patch) | |
tree | be191f56e3b3f3e48e5a1d4b0b1c905fdf1ae82f /src | |
parent | 6634726906548b00cb14cada82b3aa6fd445c8d4 (diff) | |
download | libical-git-f23716fdcb99fc95c0b82a8624bad38cb23bfa62.tar.gz |
Tests: Introducing custom memory allocation functions that allow testing for consistency and robustness of memory management.
Diffstat (limited to 'src')
-rw-r--r-- | src/test/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/test/regression-utils.c | 14 | ||||
-rw-r--r-- | src/test/regression.c | 7 | ||||
-rw-r--r-- | src/test/test-malloc.c | 180 | ||||
-rw-r--r-- | src/test/test-malloc.h | 67 |
5 files changed, 270 insertions, 0 deletions
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 9e008c86..29001dba 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -96,6 +96,8 @@ set(regression_SRCS regression-utils.c regression-recur.c regression-storage.c + test-malloc.c + test-malloc.h ) if(WITH_CXX_BINDINGS) list(APPEND regression_SRCS regression-cxx.cpp) diff --git a/src/test/regression-utils.c b/src/test/regression-utils.c index 431cef85..8daa1797 100644 --- a/src/test/regression-utils.c +++ b/src/test/regression-utils.c @@ -23,6 +23,8 @@ #include "libical/ical.h" +#include "test-malloc.h" + #include <stdlib.h> int QUIET = 0; @@ -212,7 +214,19 @@ void test_run(const char *test_name, void (*test_fcn) (void), int do_test, int h test_header(test_name, test_set); if (!headeronly && (do_test == 0 || do_test == test_set)) { + testmalloc_reset(); (*test_fcn) (); + + /* TODO: Check for memory leaks here. We could do a check like the + following but we would have to implement the test-cases in a way + that all memory is freed at the end of each test. This would include + freeing built in and cached timezones. + + ok("no memory leaked", + (global_testmalloc_statistics.mem_allocated_current == 0) + && (global_testmalloc_statistics.blocks_allocated == 0)); + */ + if (!QUIET) printf("\n"); } diff --git a/src/test/regression.c b/src/test/regression.c index 3c344f26..be3a2a20 100644 --- a/src/test/regression.c +++ b/src/test/regression.c @@ -27,6 +27,7 @@ #include "regression.h" #include "libical/astime.h" +#include "test-malloc.h" #include "libical/ical.h" #include "libicalss/icalss.h" #include "libicalvcal/icalvcal.h" @@ -5205,6 +5206,12 @@ int main(int argc, char *argv[]) int do_header = 0; int failed_count = 0; + // We specify special versions of malloc et al. that perform some extra verifications. + // Most notably they ensure, that memory allocated with icalmemory_new_buffer() is freed + // using icalmemory_free() rather than using free() directly and vice versa. Failing to + // do so would cause the test to fail with assertions or access violations. + icalmemory_set_mem_alloc_funcs(&test_malloc, &test_realloc, &test_free); + set_zone_directory(TEST_ZONEDIR); icaltimezone_set_tzid_prefix(TESTS_TZID_PREFIX); diff --git a/src/test/test-malloc.c b/src/test/test-malloc.c new file mode 100644 index 00000000..49de3b3c --- /dev/null +++ b/src/test/test-malloc.c @@ -0,0 +1,180 @@ +/*====================================================================== +FILE: test-malloc.c + +(C) COPYRIGHT 2018-2022, Markus Minichmayr <markus@tapkey.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of either: + + The LGPL as published by the Free Software Foundation, version + 2.1, available at: https://www.gnu.org/licenses/lgpl-2.1.html + + Or: + + The Mozilla Public License Version 2.0. You may obtain a copy of + the License at https://www.mozilla.org/MPL/ +======================================================================*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "test-malloc.h"
+#include "regression.h"
+
+#include <stdlib.h> +#include <string.h> +#include <assert.h> +
+
+
+struct testmalloc_statistics global_testmalloc_statistics;
+static int global_testmalloc_remaining_attempts = -1;
+
+#define TESTMALLOC_MAGIC_NO 0x1234abcd
+struct testmalloc_hdr {
+ uint32_t magic_no;
+ size_t size;
+};
+
+struct testmalloc_hdrlayout {
+
+ struct testmalloc_hdr hdr;
+ int data;
+};
+
+#define TESTMALLOC_HDR_SIZE ((size_t) &((struct testmalloc_hdrlayout*) 0)->data)
+
+
+void *test_malloc(size_t size) {
+
+ void* block;
+ struct testmalloc_hdr* hdr;
+
+ global_testmalloc_statistics.malloc_cnt++;
+ if (global_testmalloc_remaining_attempts == 0) {
+ global_testmalloc_statistics.malloc_failed_cnt++;
+ return NULL;
+ }
+
+ block = malloc(size + TESTMALLOC_HDR_SIZE);
+ if (block == NULL) {
+ global_testmalloc_statistics.malloc_failed_cnt++;
+ return NULL;
+ }
+
+ hdr = (struct testmalloc_hdr*) block;
+ hdr->magic_no = TESTMALLOC_MAGIC_NO;
+ hdr->size = size;
+
+ global_testmalloc_statistics.mem_allocated_current += size;
+ if (global_testmalloc_statistics.mem_allocated_current > global_testmalloc_statistics.mem_allocated_max) {
+ global_testmalloc_statistics.mem_allocated_max = global_testmalloc_statistics.mem_allocated_current;
+ }
+
+ global_testmalloc_statistics.blocks_allocated++;
+
+ if (global_testmalloc_remaining_attempts > 0) {
+ global_testmalloc_remaining_attempts--;
+ }
+
+ // cppcheck-suppress memleak
+ return (void*) &((struct testmalloc_hdrlayout *) hdr)->data;
+}
+
+void *test_realloc(void* p, size_t size) {
+
+ struct testmalloc_hdr* hdr;
+ size_t old_size;
+
+ global_testmalloc_statistics.realloc_cnt++;
+ if (global_testmalloc_remaining_attempts == 0) {
+ global_testmalloc_statistics.realloc_failed_cnt++;
+ return NULL;
+ }
+
+ if (p == NULL) {
+ global_testmalloc_statistics.realloc_failed_cnt++;
+ return NULL;
+ }
+
+ hdr = (struct testmalloc_hdr *) (((uint8_t *) p) - TESTMALLOC_HDR_SIZE);
+ if (hdr->magic_no != TESTMALLOC_MAGIC_NO) {
+ global_testmalloc_statistics.realloc_failed_cnt++;
+ return NULL;
+ }
+
+ old_size = hdr->size;
+ hdr->magic_no = 0;
+
+ // cppcheck-suppress memleakOnRealloc; the mem block p passed to this function stays valid.
+ hdr = (struct testmalloc_hdr*) realloc(hdr, size + TESTMALLOC_HDR_SIZE);
+ if (hdr == NULL) {
+ global_testmalloc_statistics.realloc_failed_cnt++;
+ return NULL;
+ }
+
+ hdr->magic_no = TESTMALLOC_MAGIC_NO;
+ hdr->size = size;
+
+ global_testmalloc_statistics.mem_allocated_current += size - old_size;
+ if (global_testmalloc_statistics.mem_allocated_current > global_testmalloc_statistics.mem_allocated_max) {
+ global_testmalloc_statistics.mem_allocated_max = global_testmalloc_statistics.mem_allocated_current;
+ }
+
+ if (global_testmalloc_remaining_attempts > 0) {
+ global_testmalloc_remaining_attempts--;
+ }
+
+ return (void*) &((struct testmalloc_hdrlayout*)hdr)->data;
+}
+
+void test_free(void* p) {
+
+ struct testmalloc_hdr* hdr;
+ size_t old_size;
+
+ if (p == NULL) {
+ return;
+ }
+
+ global_testmalloc_statistics.free_cnt++;
+
+ hdr = (struct testmalloc_hdr *) (((uint8_t *) p) - TESTMALLOC_HDR_SIZE);
+
+ // The main objective of this check is to ensure, that only memory, that has been allocated via icalmemory is freed
+ // via icalmemory_free(). A side objective is to make sure, the block of memory hasn't been corrupted.
+ if (hdr->magic_no != TESTMALLOC_MAGIC_NO) {
+
+ // If we end up here, then probably either of the following happened:
+ // * The calling code tries to free a block of memory via icalmemory_free() that has been allocated outside of
+ // icalmemory, e.g. via malloc().
+ // * The header in front of the memory block being freed has been corrupted.
+
+ ok("freed memory was allocated via icalmemory and has not been corrupted", hdr->magic_no == TESTMALLOC_MAGIC_NO);
+ assert(hdr->magic_no == TESTMALLOC_MAGIC_NO);
+ global_testmalloc_statistics.free_failed_cnt++;
+ return;
+ }
+
+ old_size = hdr->size;
+ hdr->magic_no = 0;
+
+ free(hdr);
+
+ global_testmalloc_statistics.mem_allocated_current -= old_size;
+ global_testmalloc_statistics.blocks_allocated--;
+}
+
+
+
+void testmalloc_reset() {
+ memset(&global_testmalloc_statistics, 0, sizeof(global_testmalloc_statistics));
+ global_testmalloc_remaining_attempts = -1;
+}
+
+/** Sets the maximum number of malloc or realloc attemts that will succeed. If
+* the number is negative, no limit will be applied. */
+void testmalloc_set_max_successful_allocs(int n) {
+ global_testmalloc_remaining_attempts = n;
+}
diff --git a/src/test/test-malloc.h b/src/test/test-malloc.h new file mode 100644 index 00000000..f66d8703 --- /dev/null +++ b/src/test/test-malloc.h @@ -0,0 +1,67 @@ +/*====================================================================== +FILE: test-malloc.h + +(C) COPYRIGHT 2018-2022, Markus Minichmayr <markus@tapkey.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of either: + + The LGPL as published by the Free Software Foundation, version + 2.1, available at: https://www.gnu.org/licenses/lgpl-2.1.html + + Or: + + The Mozilla Public License Version 2.0. You may obtain a copy of + the License at https://www.mozilla.org/MPL/ +======================================================================*/ +
+#ifndef TESTMALLOC_H +#define TESTMALLOC_H +
+#include <stdint.h>
+
+
+struct testmalloc_statistics {
+ int malloc_cnt;
+ int realloc_cnt;
+ int free_cnt;
+
+ int malloc_failed_cnt;
+ int realloc_failed_cnt;
+ int free_failed_cnt;
+
+ size_t mem_allocated_max;
+ size_t mem_allocated_current;
+ int blocks_allocated;
+};
+
+extern struct testmalloc_statistics global_testmalloc_statistics;
+
+/** Allocates the specified amount of memory and returns a pointer to the allocated memory.
+ * Memory allocated using this function must be freed using test_free().
+ * The number of allocations that can be made using this function can be limited via
+ * testmalloc_set_max_successful_allocs().
+ */
+void *test_malloc(size_t size);
+
+/** Resizes the specified buffer.
+ * Can only be used with memory that has previously been allocated using test_malloc().
+ */
+void *test_realloc(void* p, size_t size);
+
+/** Frees a block of memory that has previously been allocated via the test_malloc() function. Specifying memory that
+ * has not been allocated via test_malloc() causes an assertion.
+ */
+void test_free(void* p);
+
+/** Resets the memory management statistics and sets the number of successful
+ * allocations limit to infinite.
+ */
+void testmalloc_reset();
+
+/** Sets the maximum number of malloc or realloc attemts that will succeed. If
+ * the number is negative, no limit will be applied.
+ */
+void testmalloc_set_max_successful_allocs(int n);
+
+#endif /* !TESTMALLOC_H */
\ No newline at end of file |