summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2021-09-14 21:41:48 -0400
committerDavid Robillard <d@drobilla.net>2021-09-14 23:55:44 -0400
commit80b01dd8344c2469e943726769c5ec6b8fc53401 (patch)
tree1acda5da5943d136bc74007bd5bed16abda7de36
parent939a47fb457128358b1c6553893be26b9b4fe060 (diff)
downloadzix-80b01dd8344c2469e943726769c5ec6b8fc53401.tar.gz
zix-80b01dd8344c2469e943726769c5ec6b8fc53401.tar.bz2
zix-80b01dd8344c2469e943726769c5ec6b8fc53401.zip
Replace shared library malloc shim with explicit allocation testing
The old approach was generally annoying to deal with sometimes, and not particularly portable. This replaces it by using the new custom allocator interface with unit tests that specifically check that failed allocation is handled properly.
-rw-r--r--.gitlab-ci.yml6
-rw-r--r--meson.build5
-rw-r--r--meson_options.txt3
-rw-r--r--test/btree_test.c45
-rw-r--r--test/hash_test.c156
-rw-r--r--test/test_malloc.c84
-rw-r--r--test/test_malloc.h16
7 files changed, 127 insertions, 188 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7d936dc..5890c05 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -67,7 +67,7 @@ x64_sanitize:
<<: *build_definition
image: lv2plugin/debian-x64-clang
script:
- - meson . build -Db_lundef=false -Dbuildtype=plain -Dtest_malloc=false -Dstrict=true -Dwerror=true
+ - meson . build -Db_lundef=false -Dbuildtype=plain -Dstrict=true -Dwerror=true
- ninja -C build test
variables:
CC: "clang"
@@ -124,14 +124,14 @@ win_dbg:
<<: *build_definition
tags: [windows,meson]
script:
- - meson . build -Dbuildtype=debug -Dstrict=true -Dwerror=true -Dtest_malloc=false
+ - meson . build -Dbuildtype=debug -Dstrict=true -Dwerror=true
- ninja -C build test
win_rel:
<<: *build_definition
tags: [windows,meson]
script:
- - meson . build -Dbuildtype=release -Dstrict=true -Dwerror=true -Dtest_malloc=false
+ - meson . build -Dbuildtype=release -Dstrict=true -Dwerror=true
- ninja -C build test
diff --git a/meson.build b/meson.build
index c6412ee..d7ba9d0 100644
--- a/meson.build
+++ b/meson.build
@@ -266,11 +266,6 @@ if get_option('tests')
c_args = []
sources = files('test/@0@.c'.format(test), 'test/failing_allocator.c')
- if get_option('test_malloc') and host_machine.system() != 'windows'
- sources += ['test/test_malloc.c']
- c_args += ['-DZIX_WITH_TEST_MALLOC']
- endif
-
test(test,
executable(test,
sources,
diff --git a/meson_options.txt b/meson_options.txt
index e41ba23..659c74c 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -4,8 +4,5 @@ option('benchmarks', type: 'feature', value: 'auto', yield: true,
option('strict', type: 'boolean', value: false, yield: true,
description: 'Enable ultra-strict warnings')
-option('test_malloc', type: 'boolean', value: true,
- description: 'Test allocation failure')
-
option('tests', type: 'boolean', value: true, yield: true,
description: 'Build tests')
diff --git a/test/btree_test.c b/test/btree_test.c
index b526551..aa8cb69 100644
--- a/test/btree_test.c
+++ b/test/btree_test.c
@@ -1,12 +1,12 @@
-// Copyright 2011-2020 David Robillard <d@drobilla.net>
+// Copyright 2011-2021 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#undef NDEBUG
#include "zix/btree.h"
+#include "failing_allocator.h"
#include "test_data.h"
-#include "test_malloc.h"
#include "zix/common.h"
@@ -250,14 +250,16 @@ test_remove_cases(void)
}
static int
-stress(const unsigned test_num, const size_t n_elems)
+stress(ZixAllocator* const allocator,
+ const unsigned test_num,
+ const size_t n_elems)
{
if (n_elems == 0) {
return 0;
}
uintptr_t r = 0;
- ZixBTree* t = zix_btree_new(NULL, int_cmp, NULL);
+ ZixBTree* t = zix_btree_new(allocator, int_cmp, NULL);
ZixStatus st = ZIX_STATUS_SUCCESS;
if (!t) {
@@ -592,6 +594,22 @@ stress(const unsigned test_num, const size_t n_elems)
return EXIT_SUCCESS;
}
+static void
+test_failed_alloc(void)
+{
+ ZixFailingAllocator allocator = zix_failing_allocator();
+
+ // Successfully stress test the tree to count the number of allocations
+ assert(!stress(&allocator.base, 0, 4096));
+
+ // Test that each allocation failing is handled gracefully
+ const size_t n_new_allocs = allocator.n_allocations;
+ for (size_t i = 0u; i < n_new_allocs; ++i) {
+ allocator.n_remaining = i;
+ assert(stress(&allocator.base, 0, 4096));
+ }
+}
+
int
main(int argc, char** argv)
{
@@ -605,6 +623,7 @@ main(int argc, char** argv)
test_iter_comparison();
test_insert_split_value();
test_remove_cases();
+ test_failed_alloc();
const unsigned n_tests = 3u;
const size_t n_elems = (argc > 1) ? strtoul(argv[1], NULL, 10) : 524288u;
@@ -613,27 +632,11 @@ main(int argc, char** argv)
for (unsigned i = 0; i < n_tests; ++i) {
printf(".");
fflush(stdout);
- if (stress(i, n_elems)) {
+ if (stress(NULL, i, n_elems)) {
return EXIT_FAILURE;
}
}
printf("\n");
-#ifdef ZIX_WITH_TEST_MALLOC
- const size_t total_n_allocs = test_malloc_get_n_allocs();
- const size_t fail_n_elems = 1000;
- printf("Testing 0 ... %" PRIuPTR " failed allocations\n", total_n_allocs);
- expect_failure = true;
- for (size_t i = 0; i < total_n_allocs; ++i) {
- test_malloc_reset(i);
- stress(0, fail_n_elems);
- if (i > test_malloc_get_n_allocs()) {
- break;
- }
- }
-
- test_malloc_reset((size_t)-1);
-#endif
-
return EXIT_SUCCESS;
}
diff --git a/test/hash_test.c b/test/hash_test.c
index 7f9952d..9dbcadd 100644
--- a/test/hash_test.c
+++ b/test/hash_test.c
@@ -1,4 +1,4 @@
-// Copyright 2011-2020 David Robillard <d@drobilla.net>
+// Copyright 2011-2021 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#undef NDEBUG
@@ -6,9 +6,10 @@
#define ZIX_HASH_KEY_TYPE const char
#define ZIX_HASH_RECORD_TYPE const char
+#include "failing_allocator.h"
#include "test_data.h"
-#include "test_malloc.h"
+#include "zix/allocator.h"
#include "zix/attributes.h"
#include "zix/common.h"
#include "zix/digest.h"
@@ -25,19 +26,27 @@
static bool expect_failure = false;
-ZIX_LOG_FUNC(1, 2)
+ZIX_LOG_FUNC(4, 5)
static int
-test_fail(const char* fmt, ...)
+test_fail(ZixHash* const hash,
+ char* const buffer,
+ char** const strings,
+ const char* fmt,
+ ...)
{
- if (expect_failure) {
- return 0;
+ if (!expect_failure) {
+ va_list args;
+ va_start(args, fmt);
+ fprintf(stderr, "error: ");
+ vfprintf(stderr, fmt, args);
+ va_end(args);
}
- va_list args;
- va_start(args, fmt);
- fprintf(stderr, "error: ");
- vfprintf(stderr, fmt, args);
- va_end(args);
- return 1;
+
+ zix_hash_free(hash);
+ free(buffer);
+ free(strings);
+
+ return expect_failure ? 0 : 1;
}
ZIX_PURE_FUNC static const char*
@@ -117,11 +126,13 @@ string_equal(const char* const a, const char* const b)
}
static int
-stress_with(const ZixHashFunc hash_func, const size_t n_elems)
+stress_with(ZixAllocator* const allocator,
+ const ZixHashFunc hash_func,
+ const size_t n_elems)
{
- ZixHash* hash = zix_hash_new(NULL, identity, hash_func, string_equal);
+ ZixHash* hash = zix_hash_new(allocator, identity, hash_func, string_equal);
if (!hash) {
- return test_fail("Failed to allocate hash\n");
+ return test_fail(hash, NULL, NULL, "Failed to allocate hash\n");
}
static const size_t string_length = 15;
@@ -129,9 +140,7 @@ stress_with(const ZixHashFunc hash_func, const size_t n_elems)
char* const buffer = (char*)calloc(1, n_elems * (string_length + 1));
char** const strings = (char**)calloc(sizeof(char*), n_elems);
if (!buffer || !strings) {
- free(buffer);
- free(strings);
- return test_fail("Failed to allocate strings\n");
+ return test_fail(hash, buffer, strings, "Failed to allocate strings\n");
}
uint32_t seed = 1u;
@@ -150,22 +159,31 @@ stress_with(const ZixHashFunc hash_func, const size_t n_elems)
for (size_t i = 0; i < n_elems; ++i) {
ZixStatus st = zix_hash_insert(hash, strings[i]);
if (st) {
- return test_fail("Failed to insert `%s'\n", strings[i]);
+ return test_fail(
+ hash, buffer, strings, "Failed to insert `%s'\n", strings[i]);
}
}
// Ensure hash size is correct
if (zix_hash_size(hash) != n_elems) {
- return test_fail(
- "Hash size %" PRIuPTR " != %" PRIuPTR "\n", zix_hash_size(hash), n_elems);
+ return test_fail(hash,
+ buffer,
+ strings,
+ "Hash size %" PRIuPTR " != %" PRIuPTR "\n",
+ zix_hash_size(hash),
+ n_elems);
}
// Attempt to insert each string again
for (size_t i = 0; i < n_elems; ++i) {
ZixStatus st = zix_hash_insert(hash, strings[i]);
if (st != ZIX_STATUS_EXISTS) {
- return test_fail(
- "Double inserted `%s' (%s)\n", strings[i], zix_strerror(st));
+ return test_fail(hash,
+ buffer,
+ strings,
+ "Double inserted `%s' (%s)\n",
+ strings[i],
+ zix_strerror(st));
}
}
@@ -173,10 +191,12 @@ stress_with(const ZixHashFunc hash_func, const size_t n_elems)
for (size_t i = 0; i < n_elems; ++i) {
const char* match = (const char*)zix_hash_find_record(hash, strings[i]);
if (!match) {
- return test_fail("Failed to find `%s'\n", strings[i]);
+ return test_fail(
+ hash, buffer, strings, "Failed to find `%s'\n", strings[i]);
}
if (match != strings[i]) {
- return test_fail("Bad match for `%s': `%s'\n", strings[i], match);
+ return test_fail(
+ hash, buffer, strings, "Bad match for `%s': `%s'\n", strings[i], match);
}
}
@@ -188,7 +208,8 @@ stress_with(const ZixHashFunc hash_func, const size_t n_elems)
memcpy(not_indexed, not_indexed_string, strlen(not_indexed_string) + 1);
const char* match = (const char*)zix_hash_find_record(hash, not_indexed);
if (match) {
- return test_fail("Unexpectedly found `%s'\n", not_indexed);
+ return test_fail(
+ hash, buffer, strings, "Unexpectedly found `%s'\n", not_indexed);
}
free(not_indexed);
}
@@ -201,23 +222,31 @@ stress_with(const ZixHashFunc hash_func, const size_t n_elems)
const char* removed = NULL;
ZixStatus st = zix_hash_remove(hash, strings[i], &removed);
if (st) {
- return test_fail("Failed to remove `%s'\n", strings[i]);
+ return test_fail(
+ hash, buffer, strings, "Failed to remove `%s'\n", strings[i]);
}
// Ensure the removed value is what we expected
if (removed != strings[i]) {
- return test_fail("Removed `%s` instead of `%s`\n", removed, strings[i]);
+ return test_fail(hash,
+ buffer,
+ strings,
+ "Removed `%s` instead of `%s`\n",
+ removed,
+ strings[i]);
}
// Ensure size is updated
if (zix_hash_size(hash) != initial_size - 1) {
- return test_fail("Removing node did not decrease hash size\n");
+ return test_fail(
+ hash, buffer, strings, "Removing node did not decrease hash size\n");
}
// Ensure second removal fails
st = zix_hash_remove(hash, strings[i], &removed);
if (st != ZIX_STATUS_NOT_FOUND) {
- return test_fail("Unexpectedly removed `%s' twice\n", strings[i]);
+ return test_fail(
+ hash, buffer, strings, "Unexpectedly removed `%s' twice\n", strings[i]);
}
// Ensure value can no longer be found
@@ -227,10 +256,18 @@ stress_with(const ZixHashFunc hash_func, const size_t n_elems)
for (size_t j = i + 1; j < n_elems; ++j) {
const char* match = (const char*)zix_hash_find_record(hash, strings[j]);
if (!match) {
- return test_fail("Failed to find `%s' after remove\n", strings[j]);
+ return test_fail(hash,
+ buffer,
+ strings,
+ "Failed to find `%s' after remove\n",
+ strings[j]);
}
if (match != strings[j]) {
- return test_fail("Bad match for `%s' after remove\n", strings[j]);
+ return test_fail(hash,
+ buffer,
+ strings,
+ "Bad match for `%s' after remove\n",
+ strings[j]);
}
}
}
@@ -242,7 +279,8 @@ stress_with(const ZixHashFunc hash_func, const size_t n_elems)
assert(!zix_hash_record_at(hash, plan));
ZixStatus st = zix_hash_insert_at(hash, plan, strings[i]);
if (st) {
- return test_fail("Failed to insert `%s'\n", strings[i]);
+ return test_fail(
+ hash, buffer, strings, "Failed to insert `%s'\n", strings[i]);
}
}
@@ -253,13 +291,13 @@ stress_with(const ZixHashFunc hash_func, const size_t n_elems)
const char* const string = (const char*)zix_hash_get(hash, i);
assert(string);
if (strlen(string) < 3) {
- return test_fail("Corrupt value `%s'\n", string);
+ return test_fail(hash, buffer, strings, "Corrupt value `%s'\n", string);
}
++n_checked;
}
if (n_checked != n_elems) {
- return test_fail("Check failed\n");
+ return test_fail(hash, buffer, strings, "Check failed\n");
}
free(strings);
@@ -270,19 +308,19 @@ stress_with(const ZixHashFunc hash_func, const size_t n_elems)
}
static int
-stress(const size_t n_elems)
+stress(ZixAllocator* const allocator, const size_t n_elems)
{
- if (stress_with(decent_string_hash, n_elems) ||
- stress_with(terrible_string_hash, n_elems / 4) ||
- stress_with(string_hash_aligned, n_elems / 4) ||
- stress_with(string_hash32, n_elems / 4) ||
- stress_with(string_hash64, n_elems / 4) ||
- stress_with(string_hash32_aligned, n_elems / 4)) {
+ if (stress_with(allocator, decent_string_hash, n_elems) ||
+ stress_with(allocator, terrible_string_hash, n_elems / 4) ||
+ stress_with(allocator, string_hash_aligned, n_elems / 4) ||
+ stress_with(allocator, string_hash32, n_elems / 4) ||
+ stress_with(allocator, string_hash64, n_elems / 4) ||
+ stress_with(allocator, string_hash32_aligned, n_elems / 4)) {
return 1;
}
#if UINTPTR_MAX >= UINT64_MAX
- if (stress_with(string_hash64_aligned, n_elems / 4)) {
+ if (stress_with(allocator, string_hash64_aligned, n_elems / 4)) {
return 1;
}
#endif
@@ -349,29 +387,35 @@ test_all_tombstones(void)
#undef N_STRINGS
}
+static void
+test_failed_alloc(void)
+{
+ ZixFailingAllocator allocator = zix_failing_allocator();
+
+ // Successfully stress test the tree to count the number of allocations
+ assert(!stress(&allocator.base, 16));
+
+ // Test that each allocation failing is handled gracefully
+ const size_t n_new_allocs = allocator.n_allocations;
+ for (size_t i = 0u; i < n_new_allocs; ++i) {
+ allocator.n_remaining = i;
+ assert(stress(&allocator.base, 16));
+ }
+}
+
int
main(void)
{
zix_hash_free(NULL);
+
test_all_tombstones();
+ test_failed_alloc();
static const size_t n_elems = 1024u;
- if (stress(n_elems)) {
+ if (stress(NULL, n_elems)) {
return 1;
}
-#ifdef ZIX_WITH_TEST_MALLOC
- const size_t total_n_allocs = test_malloc_get_n_allocs();
- printf("Testing 0 ... %" PRIuPTR " failed allocations\n", total_n_allocs);
- expect_failure = true;
- for (size_t i = 0; i < total_n_allocs; ++i) {
- test_malloc_reset(i);
- stress(n_elems);
- }
-
- test_malloc_reset((size_t)-1);
-#endif
-
return 0;
}
diff --git a/test/test_malloc.c b/test/test_malloc.c
deleted file mode 100644
index 4274af2..0000000
--- a/test/test_malloc.c
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2014-2020 David Robillard <d@drobilla.net>
-// SPDX-License-Identifier: ISC
-
-#ifndef __APPLE__
-# define _GNU_SOURCE
-#endif
-
-#include "test_malloc.h"
-
-#include <dlfcn.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-
-static void* (*test_malloc_sys_malloc)(size_t size) = NULL;
-
-static size_t test_malloc_n_allocs = 0;
-static size_t test_malloc_fail_after = (size_t)-1;
-static volatile bool in_test_malloc_init = false;
-
-static void*
-test_malloc(size_t size)
-{
- if (in_test_malloc_init) {
- return NULL; // dlsym is asking for memory, but handles this fine
- }
-
- if (!test_malloc_sys_malloc) {
- test_malloc_init();
- }
-
- if (test_malloc_n_allocs < test_malloc_fail_after) {
- ++test_malloc_n_allocs;
- return test_malloc_sys_malloc(size);
- }
-
- return NULL;
-}
-
-void*
-malloc(size_t size)
-{
- return test_malloc(size);
-}
-
-void*
-calloc(size_t nmemb, size_t size)
-{
- void* ptr = test_malloc(nmemb * size);
- if (ptr) {
- memset(ptr, 0, nmemb * size);
- }
- return ptr;
-}
-
-void
-test_malloc_reset(size_t fail_after)
-{
- test_malloc_fail_after = fail_after;
- test_malloc_n_allocs = 0;
-}
-
-void
-test_malloc_init(void)
-{
- in_test_malloc_init = true;
-
- /* Avoid pedantic warnings about casting pointer to function pointer by
- casting dlsym instead. */
-
- typedef void* (*MallocFunc)(size_t);
- typedef MallocFunc (*MallocFuncGetter)(void*, const char*);
-
- MallocFuncGetter dlfunc = (MallocFuncGetter)dlsym;
- test_malloc_sys_malloc = (MallocFunc)dlfunc(RTLD_NEXT, "malloc");
-
- in_test_malloc_init = false;
-}
-
-size_t
-test_malloc_get_n_allocs(void)
-{
- return test_malloc_n_allocs;
-}
diff --git a/test/test_malloc.h b/test/test_malloc.h
deleted file mode 100644
index 7ebf944..0000000
--- a/test/test_malloc.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2014-2020 David Robillard <d@drobilla.net>
-// SPDX-License-Identifier: ISC
-
-#include "zix/attributes.h"
-
-#include <stddef.h>
-
-void
-test_malloc_init(void);
-
-void
-test_malloc_reset(size_t fail_after);
-
-ZIX_PURE_API
-size_t
-test_malloc_get_n_allocs(void);