Tests: tuktest.h: Add malloc wrapper with automatic freeing.

This commit is contained in:
Lasse Collin 2022-06-16 13:08:19 +03:00
parent 1d51536a4b
commit d8b63a0ad6
1 changed files with 124 additions and 0 deletions

View File

@ -241,6 +241,10 @@ static const char *tuktest_name = NULL;
static jmp_buf tuktest_jmpenv;
// This declaration is needed for tuktest_malloc().
static int tuktest_end(void);
// printf() is without checking its return value in many places. This function
// is called before exiting to check the status of stdout and catch errors.
static void
@ -290,6 +294,122 @@ tuktest_print_result_prefix(enum tuktest_result result,
}
// An entry for linked list of memory allocations.
struct tuktest_malloc_record {
struct tuktest_malloc_record *next;
void *p;
};
// Linked list of per-test allocations. This is used when under tuktest_run().
// These allocations are freed in tuktest_run() and, in case of a hard error,
// also in tuktest_end().
static struct tuktest_malloc_record *tuktest_malloc_test = NULL;
// Linked list of global allocations. This is used allocations are made
// outside tuktest_run(). These are freed in tuktest_end().
static struct tuktest_malloc_record *tuktest_malloc_global = NULL;
/// A wrapper for malloc() that never return NULL and the allocated memory is
/// automatically freed at the end of tuktest_run() (if allocation was done
/// within a test) or early in tuktest_end() (if allocation was done outside
/// tuktest_run()).
///
/// If allocation fails, a hard error is reported and this function won't
/// return. Possible other tests won't be run (this will call exit()).
#define tuktest_malloc(size) tuktest_malloc_impl(size, __FILE__, __LINE__)
static void *
tuktest_malloc_impl(size_t size, const char *filename, unsigned line)
{
void *p = malloc(size);
struct tuktest_malloc_record *r = malloc(sizeof(*r));
if (p == NULL || r == NULL) {
free(r);
free(p);
tuktest_print_result_prefix(TUKTEST_ERROR, filename, line);
// Avoid %zu for portability to very old systems that still
// can compile C99 code.
printf("tuktest_malloc(%" TUKTEST_PRIu ") failed\n",
(tuktest_uint)size);
++tuktest_stats[TUKTEST_ERROR];
exit(tuktest_end());
}
r->p = p;
if (tuktest_name == NULL) {
// We were called outside tuktest_run().
r->next = tuktest_malloc_global;
tuktest_malloc_global = r;
} else {
// We were called under tuktest_run().
r->next = tuktest_malloc_test;
tuktest_malloc_test = r;
}
return p;
}
/// Frees memory allocated using tuktest_malloc(). Usually this isn't needed
/// as the memory is freed automatically.
///
/// NULL is silently ignored.
///
/// NOTE: Under tuktest_run() only memory allocated there can be freed.
/// That is, allocations done outside tuktest_run() can only be freed
/// outside tuktest_run().
#define tuktest_free(ptr) tuktest_free_impl(ptr, __FILE__, __LINE__)
static void
tuktest_free_impl(void *p, const char *filename, unsigned line)
{
if (p == NULL)
return;
struct tuktest_malloc_record **r = tuktest_name != NULL
? &tuktest_malloc_test : &tuktest_malloc_global;
while (*r != NULL) {
struct tuktest_malloc_record *tmp = *r;
if (tmp->p == p) {
*r = tmp->next;
free(p);
free(tmp);
return;
}
r = &tmp->next;
}
tuktest_print_result_prefix(TUKTEST_ERROR, filename, line);
printf("tuktest_free: Allocation matching the pointer "
"was not found\n");
++tuktest_stats[TUKTEST_ERROR];
exit(tuktest_end());
}
// Frees all allocates in the given record list. The argument must be
// either &tuktest_malloc_test or &tuktest_malloc_global.
static void
tuktest_free_all(struct tuktest_malloc_record **r)
{
while (*r != NULL) {
struct tuktest_malloc_record *tmp = *r;
*r = tmp->next;
free(tmp->p);
free(tmp);
}
}
/// Initialize the test framework. No other functions or macros
/// from this file may be called before calling this.
///
@ -363,6 +483,9 @@ do { \
static int
tuktest_end(void)
{
tuktest_free_all(&tuktest_malloc_test);
tuktest_free_all(&tuktest_malloc_global);
unsigned total_tests = 0;
for (unsigned i = 0; i <= TUKTEST_ERROR; ++i)
total_tests += tuktest_stats[i];
@ -477,6 +600,7 @@ tuktest_run_test(void (*testfunc)(void), const char *testfunc_str)
exit(tuktest_end());
}
tuktest_free_all(&tuktest_malloc_test);
tuktest_name = NULL;
}