aboutsummaryrefslogtreecommitdiff
path: root/mem
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2023-07-13 08:22:18 -0700
committer3gg <3gg@shellblade.net>2023-07-13 08:22:18 -0700
commit9f254f0c7b03236be615b1235cf3fc765d6000ea (patch)
treef0b878ef2b431b909d9efd45c1f9ec8ed8ca54f8 /mem
parentfc5886c75ab2626acbc0d7b3db475d17d2cbe01f (diff)
Add mem allocator, remove listpool.
Diffstat (limited to 'mem')
-rw-r--r--mem/CMakeLists.txt26
-rw-r--r--mem/include/mem.h149
-rw-r--r--mem/src/mem.c183
-rw-r--r--mem/test/mem_test.c232
-rw-r--r--mem/test/test.h185
5 files changed, 775 insertions, 0 deletions
diff --git a/mem/CMakeLists.txt b/mem/CMakeLists.txt
new file mode 100644
index 0000000..233d2be
--- /dev/null
+++ b/mem/CMakeLists.txt
@@ -0,0 +1,26 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(mem)
4
5# Library
6
7add_library(mem
8 src/mem.c)
9
10target_include_directories(mem PUBLIC
11 include)
12
13target_link_libraries(mem
14 list)
15
16target_compile_options(mem PRIVATE -Wall -Wextra)
17
18# Test
19
20add_executable(mem_test
21 test/mem_test.c)
22
23target_link_libraries(mem_test
24 mem)
25
26target_compile_options(mem_test PRIVATE -DUNIT_TEST -DNDEBUG -Wall -Wextra)
diff --git a/mem/include/mem.h b/mem/include/mem.h
new file mode 100644
index 0000000..30c24fc
--- /dev/null
+++ b/mem/include/mem.h
@@ -0,0 +1,149 @@
1/*
2 * Block-based Memory Allocator.
3 *
4 * Clients should use the macros to define and use allocators. They make the API
5 * type-safe.
6 *
7 * Like a pool/block-based allocator, this allocator stores data in fixed-size
8 * blocks. However, this allocator also supports allocation of contiguous chunks
9 * of a variable number of blocks.
10 *
11 * Chunk information is stored in a separate array so that client data is
12 * contiguous in the main pool of memory and better cached.
13 */
14#pragma once
15
16#include <assert.h>
17#include <stdbool.h>
18#include <stddef.h>
19#include <stdint.h>
20
21/// Define a typed memory allocator backed by a statically-allocated array.
22#define DEF_MEM(MEM, TYPE, NUM_BLOCKS) \
23 typedef struct MEM { \
24 Memory mem; \
25 Chunk chunks[NUM_BLOCKS]; \
26 TYPE blocks[NUM_BLOCKS]; \
27 } MEM;
28
29/// Define a typed memory allocator backed by a dynamically-allocated array.
30#define DEF_MEM_DYN(MEM, TYPE) \
31 typedef struct MEM { \
32 Memory mem; \
33 Chunk* chunks; \
34 TYPE* blocks; \
35 } MEM;
36
37/// Initialize a statically-backed memory allocator.
38#define mem_make(MEM) \
39 { \
40 assert(MEM); \
41 const size_t block_size = sizeof((MEM)->blocks[0]); \
42 const size_t num_blocks = sizeof((MEM)->blocks) / block_size; \
43 mem_make_( \
44 &(MEM)->mem, (MEM)->chunks, (MEM)->blocks, num_blocks, block_size); \
45 }
46
47/// Initialize a dynamically-backed memory allocator.
48#define mem_make_dyn(MEM, num_blocks, block_size) \
49 mem_make_(&(MEM)->mem, 0, 0, num_blocks, block_size)
50
51/// Destroy the allocator.
52///
53/// If the allocator is dynamically-backed, then this function frees the
54/// underlying memory.
55#define mem_del(MEM) mem_del_(&(MEM)->mem)
56
57/// Clear the allocator.
58///
59/// This function frees all of the allocator's blocks. The resulting allocator
60/// is as if it were newly created.
61#define mem_clear(MEM) mem_clear_(&(MEM)->mem)
62
63/// Allocate a new chunk of N blocks.
64/// Return a pointer to the first block of the chunk, or 0 if there is no memory
65/// left.
66/// New chunks are conveniently zeroed out.
67#define mem_alloc(MEM, num_blocks) mem_alloc_(&(MEM)->mem, num_blocks)
68
69/// Free the chunk.
70/// The chunk pointer is conveniently set to 0.
71#define mem_free(MEM, CHUNK) mem_free_(&(MEM)->mem, (void**)CHUNK)
72
73/// Return a pointer to a chunk given the chunk's handle.
74/// The chunk must have been allocated.
75#define mem_get_chunk(MEM, HANDLE) \
76 ((__typeof__((MEM)->blocks[0])*)mem_get_chunk_(&(MEM)->mem, HANDLE))
77
78/// Get the handle to the given chunk.
79#define mem_get_chunk_handle(MEM, CHUNK_PTR) \
80 mem_get_chunk_handle_(&(MEM)->mem, CHUNK_PTR)
81
82/// Iterate over the used chunks of the allocator.
83///
84/// The caller can use 'i' as the index of the current chunk.
85///
86/// It is valid to mem_free() the chunk at each step of the iteration.
87#define mem_foreach(MEM, ITER, BODY) \
88 size_t i = 0; \
89 do { \
90 if ((MEM)->chunks[i].used) { \
91 __typeof__((MEM)->blocks[0])* ITER = &(MEM)->blocks[i]; \
92 (void)ITER; \
93 BODY; \
94 } \
95 i = (MEM)->chunks[i].next; \
96 } while (i);
97
98// -----------------------------------------------------------------------------
99
100/// Chunk information.
101///
102/// Every chunk represents a contiguous array of some number of blocks. The
103/// allocator begins as one big unused chunk.
104///
105/// Allocation looks for a free chunk large enough to hold to requested number
106/// of blocks. If the free chunk is larger than the requested chunk size, then
107/// the requested chunk is carved out of the larger block.
108///
109/// Deallocation frees the chunk back and merges it with free neighbouring
110/// chunks. Two free chunks are never contiguous in memory.
111///
112/// 'next' and 'prev' always point to a valid chunk (e.g., 0). Allocation stops
113/// looking for free chunks when it loops over.
114typedef struct Chunk {
115 size_t num_blocks;
116 size_t prev;
117 size_t next;
118 bool used;
119} Chunk;
120
121typedef struct Memory {
122 size_t block_size_bytes;
123 size_t num_blocks;
124 size_t next_free_chunk;
125 bool dynamic; /// True if blocks and chunks are dynamically-allocated.
126 Chunk* chunks; /// Array of chunk information.
127 uint8_t* blocks; /// Array of blocks;
128} Memory;
129
130/// Create a memory allocator.
131///
132/// 'chunks' and 'blocks' may be user-provided (statically-backed allocator) or
133/// null (dynamically-backed allocator).
134/// - If null, the allocator malloc()s the memory for them.
135/// - If given:
136/// - `chunks` must be at least `num_blocks` chunks.
137/// - `blocks` must be at least `num_blocks` * `block_size_bytes` bytes.
138///
139/// All blocks are zeroed out for convenience.
140bool mem_make_(
141 Memory* mem, Chunk* chunks, void* blocks, size_t num_blocks,
142 size_t block_size_bytes);
143
144void mem_del_(Memory*);
145void mem_clear_(Memory*);
146void* mem_alloc_(Memory*, size_t num_blocks);
147void mem_free_(Memory*, void** chunk_ptr);
148void* mem_get_chunk_(const Memory*, size_t chunk_handle);
149size_t mem_get_chunk_handle_(const Memory*, const void* chunk);
diff --git a/mem/src/mem.c b/mem/src/mem.c
new file mode 100644
index 0000000..ff97f0f
--- /dev/null
+++ b/mem/src/mem.c
@@ -0,0 +1,183 @@
1#include "mem.h"
2
3#include <stdlib.h>
4#include <string.h>
5
6bool mem_make_(
7 Memory* mem, Chunk* chunks, void* blocks, size_t num_blocks,
8 size_t block_size_bytes) {
9 assert(mem);
10 assert((chunks && blocks) || (!chunks && !blocks));
11 assert(num_blocks >= 1);
12
13 mem->block_size_bytes = block_size_bytes;
14 mem->num_blocks = num_blocks;
15 mem->next_free_chunk = 0;
16
17 // Allocate chunks and blocks if necessary and zero them out.
18 if (!chunks) {
19 chunks = calloc(num_blocks, sizeof(Chunk));
20 blocks = calloc(num_blocks, block_size_bytes);
21 mem->dynamic = true;
22 if (!chunks || !blocks) {
23 return false;
24 }
25 } else {
26 memset(blocks, 0, num_blocks * block_size_bytes);
27 memset(chunks, 0, num_blocks * sizeof(Chunk));
28 mem->dynamic = false;
29 }
30 mem->chunks = chunks;
31 mem->blocks = blocks;
32
33 // Initialize the head as one large free chunk.
34 Chunk* head = &mem->chunks[0];
35 head->num_blocks = num_blocks;
36
37 return true;
38}
39
40void mem_del_(Memory* mem) {
41 assert(mem);
42 if (mem->dynamic) {
43 if (mem->chunks) {
44 free(mem->chunks);
45 mem->chunks = 0;
46 }
47 if (mem->blocks) {
48 free(mem->blocks);
49 mem->blocks = 0;
50 }
51 }
52}
53
54void mem_clear_(Memory* mem) {
55 assert(mem);
56 mem->next_free_chunk = 0;
57 memset(mem->blocks, 0, mem->num_blocks * mem->block_size_bytes);
58 memset(mem->chunks, 0, mem->num_blocks * sizeof(Chunk));
59
60 // Initialize the head as one large free chunk.
61 Chunk* head = &mem->chunks[0];
62 head->num_blocks = mem->num_blocks;
63}
64
65void* mem_alloc_(Memory* mem, size_t num_blocks) {
66 assert(mem);
67 assert(num_blocks >= 1);
68
69 // Search for the first free chunk that can accommodate num_blocks.
70 const size_t start = mem->next_free_chunk;
71 size_t chunk_idx = start;
72 bool found = false;
73 do {
74 Chunk* chunk = &mem->chunks[chunk_idx];
75 if (!chunk->used) {
76 if (chunk->num_blocks > num_blocks) {
77 // Carve out a smaller chunk when the found chunk is larger than
78 // requested.
79 // [prev] <--> [chunk] <--> [new next] <--> [next]
80 const size_t new_next_idx = chunk_idx + num_blocks;
81 Chunk* new_next = &mem->chunks[new_next_idx];
82 if (chunk->next) {
83 mem->chunks[chunk->next].prev = new_next_idx;
84 }
85 new_next->prev = chunk_idx;
86 new_next->next = chunk->next;
87 chunk->next = new_next_idx;
88
89 new_next->num_blocks = chunk->num_blocks - num_blocks;
90 chunk->num_blocks = num_blocks;
91
92 chunk->used = true;
93 found = true;
94 break;
95 } else if (chunk->num_blocks == num_blocks) {
96 chunk->used = true;
97 found = true;
98 break;
99 }
100 }
101 chunk_idx = chunk->next; // Last chunk points back to 0, which is always the
102 // start of some chunk. 'next' and 'prev' are
103 // always valid pointers.
104 } while (chunk_idx != start);
105
106 if (found) {
107 mem->next_free_chunk = mem->chunks[chunk_idx].next;
108 return &mem->blocks[chunk_idx * mem->block_size_bytes];
109 } else {
110 return 0; // Large-enough free chunk not found.
111 }
112}
113
114// The given pointer is a pointer to this first block of the chunk.
115void mem_free_(Memory* mem, void** chunk_ptr) {
116 assert(mem);
117 assert(chunk_ptr);
118
119 const size_t chunk_idx =
120 ((uint8_t*)*chunk_ptr - mem->blocks) / mem->block_size_bytes;
121 assert(chunk_idx < mem->num_blocks);
122 Chunk* chunk = &mem->chunks[chunk_idx];
123
124 // Disallow double-frees.
125 assert(chunk->used);
126
127 // Zero out the chunk so that we don't get stray values the next time it is
128 // allocated.
129 memset(&mem->blocks[chunk_idx], 0, chunk->num_blocks * mem->block_size_bytes);
130
131 // Free the chunk. If it is contiguous with other free chunks, then merge.
132 // We only need to look at the chunk's immediate neighbours because no two
133 // free chunks are left contiguous after merging.
134 chunk->used = false;
135 if (chunk->next) {
136 Chunk* next = &mem->chunks[chunk->next];
137 if (!next->used) {
138 // Pre: [chunk] <--> [next] <--> [next next]
139 // Post: [ chunk + next ] <--> [next next]
140 chunk->num_blocks += mem->chunks[chunk->next].num_blocks;
141 chunk->next = next->next;
142 if (next->next) {
143 Chunk* next_next = &mem->chunks[next->next];
144 next_next->prev = chunk_idx;
145 }
146 next->prev = next->next = next->num_blocks = 0;
147 }
148 }
149 if (chunk->prev) {
150 Chunk* prev = &mem->chunks[chunk->prev];
151 if (!prev->used) {
152 // Pre: [prev] <--> [chunk] <--> [next]
153 // Post: [ prev + chunk ] <--> [next]
154 prev->num_blocks += chunk->num_blocks;
155 prev->next = chunk->next;
156 if (chunk->next) {
157 Chunk* next = &mem->chunks[chunk->next];
158 next->prev = chunk->prev;
159 }
160 chunk->prev = chunk->next = chunk->num_blocks = 0;
161 }
162 }
163
164 *chunk_ptr = 0;
165}
166
167// The handle is the chunk's index. We don't call it an index in the public API
168// because from the user's perspective, two chunks allocated back-to-back need
169// not be +1 away (the offset depends on how large the first chunk is).
170void* mem_get_chunk_(const Memory* mem, size_t chunk_handle) {
171 assert(mem);
172 assert(chunk_handle < mem->num_blocks);
173 assert(mem->chunks[chunk_handle].used);
174 return &mem->blocks[chunk_handle * mem->block_size_bytes];
175}
176
177// The given chunk pointer is a pointer to the blocks array.
178size_t mem_get_chunk_handle_(const Memory* mem, const void* chunk) {
179 assert(mem);
180 const size_t block_byte_index = (const uint8_t*)chunk - mem->blocks;
181 assert(block_byte_index % mem->block_size_bytes == 0);
182 return block_byte_index / mem->block_size_bytes;
183}
diff --git a/mem/test/mem_test.c b/mem/test/mem_test.c
new file mode 100644
index 0000000..6ab4c7c
--- /dev/null
+++ b/mem/test/mem_test.c
@@ -0,0 +1,232 @@
1#include "mem.h"
2
3#include "test.h"
4
5#define NUM_BLOCKS 10
6
7DEF_MEM(test_mem, int, NUM_BLOCKS);
8
9static int count(test_mem* mem) {
10 int count = 0;
11 mem_foreach(mem, n, { count++; });
12 return count;
13}
14
15static int sum(test_mem* mem) {
16 int sum = 0;
17 mem_foreach(mem, n, { sum += *n; });
18 return sum;
19}
20
21// Create a statically-backed allocator.
22TEST_CASE(mem_create) {
23 test_mem mem;
24 mem_make(&mem);
25}
26
27// Create a dynamically-backed allocator.
28TEST_CASE(mem_create_dyn) {
29 DEF_MEM_DYN(dyn_mem, int);
30
31 dyn_mem mem;
32 mem_make_dyn(&mem, NUM_BLOCKS, sizeof(int));
33}
34
35// Allocate N chunks of 1 block each.
36TEST_CASE(mem_fully_allocate) {
37 test_mem mem;
38 mem_make(&mem);
39
40 for (int i = 0; i < NUM_BLOCKS; ++i) {
41 const int* block = mem_alloc(&mem, 1);
42 TEST_TRUE(block != 0);
43 }
44}
45
46// Allocate N chunks of 1 block each, then free them.
47TEST_CASE(mem_fill_then_free) {
48 test_mem mem;
49 mem_make(&mem);
50
51 int* blocks[NUM_BLOCKS] = {0};
52 for (int i = 0; i < NUM_BLOCKS; i++) {
53 blocks[i] = mem_alloc(&mem, 1);
54 TEST_TRUE(blocks[i] != 0);
55 }
56
57 for (int i = 0; i < NUM_BLOCKS; i++) {
58 mem_free(&mem, &blocks[i]);
59 TEST_EQUAL(blocks[i], 0); // Pointer should be set to 0 on free.
60 }
61
62 TEST_EQUAL(count(&mem), 0);
63}
64
65// Attempt to allocate blocks past the maximum allocator size.
66// The allocator should handle the failed allocations gracefully.
67TEST_CASE(mem_allocate_beyond_max_size) {
68 test_mem mem;
69 mem_make(&mem);
70
71 // Fully allocate the mem.
72 for (int i = 0; i < NUM_BLOCKS; ++i) {
73 TEST_TRUE(mem_alloc(&mem, 1) != 0);
74 }
75
76 // Past the end.
77 for (int i = 0; i < NUM_BLOCKS; ++i) {
78 TEST_EQUAL(mem_alloc(&mem, 1), 0);
79 }
80}
81
82// Free blocks should always remain zeroed out.
83// This tests the invariant right after creating the allocator.
84TEST_CASE(mem_zero_free_blocks_after_creation) {
85 test_mem mem;
86 mem_make(&mem);
87
88 const int zero = 0;
89 for (int i = 0; i < NUM_BLOCKS; ++i) {
90 const int* block = (const int*)(mem.blocks) + i;
91 TEST_EQUAL(memcmp(block, &zero, sizeof(int)), 0);
92 }
93}
94
95// Free blocks should always remain zeroed out.
96// This tests the invariant after freeing a block.
97TEST_CASE(mem_zero_free_block_after_free) {
98 test_mem mem;
99 mem_make(&mem);
100
101 int* val = mem_alloc(&mem, 1);
102 TEST_TRUE(val != 0);
103 *val = 177;
104
105 int* old_val = val;
106 mem_free(&mem, &val); // val pointer is set to 0.
107 TEST_EQUAL(*old_val, 0); // Block is zeroed out after free.
108}
109
110// Traverse an empty allocator.
111TEST_CASE(mem_traverse_empty) {
112 test_mem mem;
113 mem_make(&mem);
114
115 TEST_EQUAL(count(&mem), 0);
116}
117
118// Traverse a partially full allocator.
119TEST_CASE(mem_traverse_partially_full) {
120 const int N = NUM_BLOCKS / 2;
121
122 test_mem mem;
123 mem_make(&mem);
124
125 for (int i = 0; i < N; ++i) {
126 int* val = mem_alloc(&mem, 1);
127 TEST_TRUE(val != 0);
128 *val = i + 1;
129 }
130
131 TEST_EQUAL(sum(&mem), (N) * (N + 1) / 2);
132}
133
134// Traverse a full allocator.
135TEST_CASE(mem_traverse_full) {
136 test_mem mem;
137 mem_make(&mem);
138
139 for (int i = 0; i < NUM_BLOCKS; ++i) {
140 int* val = mem_alloc(&mem, 1);
141 TEST_TRUE(val != 0);
142 *val = i + 1;
143 }
144
145 TEST_EQUAL(sum(&mem), (NUM_BLOCKS) * (NUM_BLOCKS + 1) / 2);
146}
147
148// Get the ith (allocated) chunk.
149TEST_CASE(mem_get_block) {
150 test_mem mem;
151 mem_make(&mem);
152
153 for (int i = 0; i < NUM_BLOCKS; ++i) {
154 int* block = mem_alloc(&mem, 1);
155 TEST_TRUE(block != 0);
156 *block = i;
157 TEST_EQUAL(mem_get_chunk_handle(&mem, block), (size_t)i);
158 }
159
160 for (int i = 0; i < NUM_BLOCKS; ++i) {
161 TEST_EQUAL(*mem_get_chunk(&mem, i), i);
162 }
163}
164
165// Test merging.
166// 1. Allocate chunks of variable sizes.
167// 2. Free them in a different order.
168// 3. Then we should be able to allocate 1 chunk of N blocks.
169TEST_CASE(mem_fragmentation) {
170 test_mem mem;
171 mem_make(&mem);
172
173 int* blocks[NUM_BLOCKS] = {0};
174 int next_block = 0;
175
176#define ALLOC(num_blocks) \
177 blocks[next_block] = mem_alloc(&mem, num_blocks); \
178 TEST_TRUE(blocks[next_block] != 0); \
179 next_block++;
180
181#define FREE(block_idx) mem_free(&mem, &blocks[block_idx])
182
183 // 5 total allocations of variable chunk sizes.
184 ALLOC(2); // 2; idx = 0
185 ALLOC(3); // 5; idx = 1
186 ALLOC(1); // 6; idx = 2
187 ALLOC(3); // 9; idx = 3
188 ALLOC(1); // 10; idx = 4
189
190 // Free the 5 allocations in a different order.
191 FREE(1);
192 FREE(3);
193 FREE(4);
194 FREE(2);
195 FREE(0);
196
197 // Should be able to allocate 1 chunk of N blocks.
198 const void* chunk = mem_alloc(&mem, NUM_BLOCKS);
199 TEST_TRUE(chunk != 0);
200}
201
202// Clear and re-use an allocator.
203TEST_CASE(mem_clear_then_reuse) {
204 test_mem mem;
205 mem_make(&mem);
206
207 // Allocate chunks, contents not important.
208 for (int i = 0; i < NUM_BLOCKS; ++i) {
209 int* chunk = mem_alloc(&mem, 1);
210 TEST_TRUE(chunk != 0);
211 }
212
213 mem_clear(&mem);
214
215 // Allocate chunks and assign values 0..N.
216 for (int i = 0; i < NUM_BLOCKS; ++i) {
217 int* chunk = mem_alloc(&mem, 1);
218 TEST_TRUE(chunk != 0);
219 *chunk = i + 1;
220 }
221
222 TEST_EQUAL(sum(&mem), NUM_BLOCKS * (NUM_BLOCKS + 1) / 2);
223}
224
225// Stress test.
226//
227// 1. Allocate the mem, either fully or partially. If fully, attempt to
228// allocate some items past the end.
229//
230// 2. Free all allocated items in some random order.
231
232int main() { return 0; }
diff --git a/mem/test/test.h b/mem/test/test.h
new file mode 100644
index 0000000..fd8dc22
--- /dev/null
+++ b/mem/test/test.h
@@ -0,0 +1,185 @@
1// SPDX-License-Identifier: MIT
2#pragma once
3
4#ifdef UNIT_TEST
5
6#include <stdbool.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
12 defined(__NetBSD__) || defined(__OpenBSD__)
13#define USE_SYSCTL_FOR_ARGS 1
14// clang-format off
15#include <sys/types.h>
16#include <sys/sysctl.h>
17// clang-format on
18#include <unistd.h> // getpid
19#endif
20
21struct test_file_metadata;
22
23struct test_failure {
24 bool present;
25 const char *message;
26 const char *file;
27 int line;
28};
29
30struct test_case_metadata {
31 void (*fn)(struct test_case_metadata *, struct test_file_metadata *);
32 struct test_failure failure;
33 const char *name;
34 struct test_case_metadata *next;
35};
36
37struct test_file_metadata {
38 bool registered;
39 const char *name;
40 struct test_file_metadata *next;
41 struct test_case_metadata *tests;
42};
43
44struct test_file_metadata __attribute__((weak)) * test_file_head;
45
46#define SET_FAILURE(_message) \
47 metadata->failure = (struct test_failure) { \
48 .message = _message, .file = __FILE__, .line = __LINE__, .present = true, \
49 }
50
51#define TEST_EQUAL(a, b) \
52 do { \
53 if ((a) != (b)) { \
54 SET_FAILURE(#a " != " #b); \
55 return; \
56 } \
57 } while (0)
58
59#define TEST_TRUE(a) \
60 do { \
61 if (!(a)) { \
62 SET_FAILURE(#a " is not true"); \
63 return; \
64 } \
65 } while (0)
66
67#define TEST_STREQUAL(a, b) \
68 do { \
69 if (strcmp(a, b) != 0) { \
70 SET_FAILURE(#a " != " #b); \
71 return; \
72 } \
73 } while (0)
74
75#define TEST_CASE(_name) \
76 static void __test_h_##_name(struct test_case_metadata *, \
77 struct test_file_metadata *); \
78 static struct test_file_metadata __test_h_file; \
79 static struct test_case_metadata __test_h_meta_##_name = { \
80 .name = #_name, \
81 .fn = __test_h_##_name, \
82 }; \
83 static void __attribute__((constructor(101))) __test_h_##_name##_register(void) { \
84 __test_h_meta_##_name.next = __test_h_file.tests; \
85 __test_h_file.tests = &__test_h_meta_##_name; \
86 if (!__test_h_file.registered) { \
87 __test_h_file.name = __FILE__; \
88 __test_h_file.next = test_file_head; \
89 test_file_head = &__test_h_file; \
90 __test_h_file.registered = true; \
91 } \
92 } \
93 static void __test_h_##_name( \
94 struct test_case_metadata *metadata __attribute__((unused)), \
95 struct test_file_metadata *file_metadata __attribute__((unused)))
96
97extern void __attribute__((weak)) (*test_h_unittest_setup)(void);
98/// Run defined tests, return true if all tests succeeds
99/// @param[out] tests_run if not NULL, set to whether tests were run
100static inline void __attribute__((constructor(102))) run_tests(void) {
101 bool should_run = false;
102#ifdef USE_SYSCTL_FOR_ARGS
103 int mib[] = {
104 CTL_KERN,
105#if defined(__NetBSD__) || defined(__OpenBSD__)
106 KERN_PROC_ARGS,
107 getpid(),
108 KERN_PROC_ARGV,
109#else
110 KERN_PROC,
111 KERN_PROC_ARGS,
112 getpid(),
113#endif
114 };
115 char *arg = NULL;
116 size_t arglen;
117 sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &arglen, NULL, 0);
118 arg = malloc(arglen);
119 sysctl(mib, sizeof(mib) / sizeof(mib[0]), arg, &arglen, NULL, 0);
120#else
121 FILE *cmdlinef = fopen("/proc/self/cmdline", "r");
122 char *arg = NULL;
123 int arglen;
124 fscanf(cmdlinef, "%ms%n", &arg, &arglen);
125 fclose(cmdlinef);
126#endif
127 for (char *pos = arg; pos < arg + arglen; pos += strlen(pos) + 1) {
128 if (strcmp(pos, "--unittest") == 0) {
129 should_run = true;
130 break;
131 }
132 }
133 free(arg);
134
135 if (!should_run) {
136 return;
137 }
138
139 if (&test_h_unittest_setup) {
140 test_h_unittest_setup();
141 }
142
143 struct test_file_metadata *i = test_file_head;
144 int failed = 0, success = 0;
145 while (i) {
146 fprintf(stderr, "Running tests from %s:\n", i->name);
147 struct test_case_metadata *j = i->tests;
148 while (j) {
149 fprintf(stderr, "\t%s ... ", j->name);
150 j->failure.present = false;
151 j->fn(j, i);
152 if (j->failure.present) {
153 fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message,
154 j->failure.file, j->failure.line);
155 failed++;
156 } else {
157 fprintf(stderr, "passed\n");
158 success++;
159 }
160 j = j->next;
161 }
162 fprintf(stderr, "\n");
163 i = i->next;
164 }
165 int total = failed + success;
166 fprintf(stderr, "Test results: passed %d/%d, failed %d/%d\n", success, total,
167 failed, total);
168 exit(failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
169}
170
171#else
172
173#include <stdbool.h>
174
175#define TEST_CASE(name) static void __attribute__((unused)) __test_h_##name(void)
176
177#define TEST_EQUAL(a, b) \
178 (void)(a); \
179 (void)(b)
180#define TEST_TRUE(a) (void)(a)
181#define TEST_STREQUAL(a, b) \
182 (void)(a); \
183 (void)(b)
184
185#endif