summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMarkus Minichmayr <markus@tapkey.com>2018-03-05 16:59:07 +0100
committerMarkus Minichmayr <markus@tapkey.com>2022-02-15 08:54:27 +0100
commitf23716fdcb99fc95c0b82a8624bad38cb23bfa62 (patch)
treebe191f56e3b3f3e48e5a1d4b0b1c905fdf1ae82f /src
parent6634726906548b00cb14cada82b3aa6fd445c8d4 (diff)
downloadlibical-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.txt2
-rw-r--r--src/test/regression-utils.c14
-rw-r--r--src/test/regression.c7
-rw-r--r--src/test/test-malloc.c180
-rw-r--r--src/test/test-malloc.h67
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