aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2021-12-04 16:01:12 -0800
committer3gg <3gg@shellblade.net>2021-12-04 16:01:12 -0800
commitf8217d240d598f39f70047f7a623dd46312542c6 (patch)
tree4e40843d665e388416c1226f739c2b8c0b8da736
parent5f6ea503cdb6ad4a95b679672a1ad324d96c89a5 (diff)
Initial commit.
-rw-r--r--CMakeLists.txt9
-rw-r--r--README.md4
-rw-r--r--cstring/CMakeLists.txt11
-rw-r--r--cstring/include/cstring.h64
-rw-r--r--list/CMakeLists.txt23
-rw-r--r--list/include/list.h21
-rw-r--r--list/src/list.c14
-rw-r--r--list/test/list_test.c34
-rw-r--r--list/test/test.h185
-rw-r--r--listpool/CMakeLists.txt26
-rw-r--r--listpool/README.md14
-rw-r--r--listpool/include/listpool.h79
-rw-r--r--listpool/src/listpool.c78
-rw-r--r--listpool/test/listpool_test.c166
-rw-r--r--listpool/test/test.h185
-rw-r--r--log/CMakeLists.txt9
-rw-r--r--log/README.md1
-rw-r--r--log/include/log/log.h19
-rw-r--r--log/src/log.c1
-rw-r--r--mempool/CMakeLists.txt23
-rw-r--r--mempool/README.md20
-rw-r--r--mempool/include/mempool.h91
-rw-r--r--mempool/src/mempool.c85
-rw-r--r--mempool/test/mempool_test.c157
-rw-r--r--mempool/test/test.h185
25 files changed, 1502 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..36df847
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,9 @@
1cmake_minimum_required(VERSION 3.16)
2
3project(clib)
4
5add_subdirectory(cstring)
6add_subdirectory(list)
7add_subdirectory(listpool)
8add_subdirectory(log)
9add_subdirectory(mempool)
diff --git a/README.md b/README.md
index 941fc04..4cab9fe 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
1# clib 1# C Library
2 2
3A collection of small C libraries. \ No newline at end of file 3A collection of small C libraries.
diff --git a/cstring/CMakeLists.txt b/cstring/CMakeLists.txt
new file mode 100644
index 0000000..67fb366
--- /dev/null
+++ b/cstring/CMakeLists.txt
@@ -0,0 +1,11 @@
1cmake_minimum_required(VERSION 3.16)
2
3project(cstring)
4
5add_library(cstring INTERFACE)
6
7target_include_directories(cstring INTERFACE
8 include)
9
10target_link_libraries(cstring INTERFACE
11 -lbsd)
diff --git a/cstring/include/cstring.h b/cstring/include/cstring.h
new file mode 100644
index 0000000..644f1e1
--- /dev/null
+++ b/cstring/include/cstring.h
@@ -0,0 +1,64 @@
1/// Fixed-size strings with value semantics.
2#pragma once
3
4#include <assert.h>
5#include <bsd/string.h>
6
7/// A fixed-size string.
8/// The string is null-terminated so that it can be used with the usual C APIs.
9#define DEF_STRING(STRING, SIZE) \
10 typedef struct STRING { \
11 int length; \
12 char str[SIZE]; \
13 } STRING; \
14 \
15 static const size_t STRING##_size = SIZE; \
16 \
17 static inline const char* STRING##_cstring(const STRING* str) { \
18 return str->str; \
19 } \
20 \
21 static inline STRING STRING##_make(const char* cstr) { \
22 if (!cstr) { \
23 return (STRING){0}; \
24 } else { \
25 STRING str = (STRING){0}; \
26 str.length = strlcpy(str.str, cstr, SIZE); \
27 return str; \
28 } \
29 } \
30 \
31 static inline STRING STRING##_dirname(STRING path) { \
32 STRING str = path; \
33 for (int i = str.length - 1; i >= 0; --i) { \
34 if (str.str[i] == '/' || str.str[i] == '\\') { \
35 str.str[i] = 0; \
36 str.length = i; \
37 return str; \
38 } else { \
39 str.str[i] = 0; \
40 } \
41 } \
42 str = (STRING){0}; \
43 str.str[0] = '.'; \
44 str.length = 1; \
45 return str; \
46 } \
47 \
48 static inline STRING STRING##_concat(STRING a, STRING b) { \
49 assert(a.length + b.length + 1 < SIZE); \
50 STRING str = {0}; \
51 strlcpy(str.str, a.str, SIZE); \
52 strlcpy(str.str + a.length, b.str, SIZE); \
53 str.length = a.length + b.length; \
54 return str; \
55 } \
56 \
57 static inline STRING STRING##_concat_path(STRING a, STRING b) { \
58 return STRING##_concat(STRING##_concat(a, STRING##_make("/")), b); \
59 }
60
61DEF_STRING(sstring, 32) // Small.
62DEF_STRING(mstring, 256) // Medium.
63DEF_STRING(lstring, 1024) // Large.
64DEF_STRING(xlstring, 4096) // Extra large.
diff --git a/list/CMakeLists.txt b/list/CMakeLists.txt
new file mode 100644
index 0000000..5d11d28
--- /dev/null
+++ b/list/CMakeLists.txt
@@ -0,0 +1,23 @@
1cmake_minimum_required(VERSION 3.16)
2
3project(list)
4
5# Library
6
7add_library(list
8 src/list.c)
9
10target_include_directories(list PUBLIC
11 include)
12
13target_compile_options(list PRIVATE -Wall -Wextra)
14
15# Test
16
17add_executable(list_test
18 test/list_test.c)
19
20target_link_libraries(list_test
21 list)
22
23target_compile_options(list_test PRIVATE -DUNIT_TEST -Wall -Wextra)
diff --git a/list/include/list.h b/list/include/list.h
new file mode 100644
index 0000000..b00b48b
--- /dev/null
+++ b/list/include/list.h
@@ -0,0 +1,21 @@
1/// A doubly linked list.
2///
3/// This list does not hold user data. Instead, the list can be used as an
4/// intrusive list or as part as a more complex data structure.
5#pragma once
6
7#include <stddef.h>
8
9typedef struct list list;
10
11typedef struct list {
12 list* prev;
13 list* next;
14} list;
15
16/// Create a new list from an array of `size` items.
17void list_make(list* list, size_t size);
18
19/// Iterates over all the items in the list.
20#define list_foreach(LIST, iter) \
21 for (struct list* iter = LIST; iter; iter = iter->next)
diff --git a/list/src/list.c b/list/src/list.c
new file mode 100644
index 0000000..f5b6507
--- /dev/null
+++ b/list/src/list.c
@@ -0,0 +1,14 @@
1#include "list.h"
2
3#include <assert.h>
4
5void list_make(list* list, size_t size) {
6 if (size == 0) {
7 return;
8 }
9 assert(list);
10 for (size_t i = 0; i < size; ++i) {
11 list[i].prev = (i == 0 ? 0 : &list[i - 1]);
12 list[i].next = (i == size - 1 ? 0 : &list[i + 1]);
13 }
14}
diff --git a/list/test/list_test.c b/list/test/list_test.c
new file mode 100644
index 0000000..a11c713
--- /dev/null
+++ b/list/test/list_test.c
@@ -0,0 +1,34 @@
1#include "list.h"
2
3#include "test.h"
4
5#define TEST_LIST_SIZE 10
6
7// Create an empty list.
8TEST_CASE(list_create_empty) { list_make(0, 0); }
9
10// Create a list of a given size.
11TEST_CASE(list_create) {
12 struct list list[TEST_LIST_SIZE];
13 list_make(list, TEST_LIST_SIZE);
14}
15
16// Iterate over a list.
17TEST_CASE(list_traverse) {
18 int numbers[TEST_LIST_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
19
20 struct list list[TEST_LIST_SIZE];
21 list_make(list, TEST_LIST_SIZE);
22
23 int count = 0;
24 int sum = 0;
25 list_foreach(list, item) {
26 count++;
27 sum += numbers[item - list];
28 }
29
30 TEST_EQUAL(count, TEST_LIST_SIZE);
31 TEST_EQUAL(sum, TEST_LIST_SIZE * (TEST_LIST_SIZE + 1) / 2);
32}
33
34int main() { return 0; }
diff --git a/list/test/test.h b/list/test/test.h
new file mode 100644
index 0000000..fd8dc22
--- /dev/null
+++ b/list/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
diff --git a/listpool/CMakeLists.txt b/listpool/CMakeLists.txt
new file mode 100644
index 0000000..6522d1f
--- /dev/null
+++ b/listpool/CMakeLists.txt
@@ -0,0 +1,26 @@
1cmake_minimum_required(VERSION 3.16)
2
3project(listpool)
4
5# Library
6
7add_library(listpool
8 src/listpool.c)
9
10target_include_directories(listpool PUBLIC
11 include)
12
13target_link_libraries(listpool
14 list)
15
16target_compile_options(listpool PRIVATE -Wall -Wextra)
17
18# Test
19
20add_executable(listpool_test
21 test/listpool_test.c)
22
23target_link_libraries(listpool_test
24listpool)
25
26target_compile_options(listpool_test PRIVATE -DUNIT_TEST -DNDEBUG -Wall -Wextra)
diff --git a/listpool/README.md b/listpool/README.md
new file mode 100644
index 0000000..ed38980
--- /dev/null
+++ b/listpool/README.md
@@ -0,0 +1,14 @@
1# Listpool
2
3A block allocator built from a single, contiguous array of memory that maintains
4free and used blocks in doubly linked lists.
5
6A `listpool` is similar to a `mempool`, but the additional structure allows it
7to:
8
9- Allocate and free blocks in constant time.
10- Traverse used blocks in linear time in the number of used blocks, as opposed
11 to the total number of blocks like in a `mempool`.
12
13A `listpool` otherwise provides the same guarantees and characteristics as a
14`mempool`.
diff --git a/listpool/include/listpool.h b/listpool/include/listpool.h
new file mode 100644
index 0000000..a5e4955
--- /dev/null
+++ b/listpool/include/listpool.h
@@ -0,0 +1,79 @@
1#pragma once
2
3#include "list.h"
4
5#include <assert.h>
6#include <stddef.h>
7#include <stdint.h>
8
9/// Define a typed listpool of a given size.
10#define DEF_LISTPOOL(POOL, TYPE, NUM_BLOCKS) \
11 typedef struct POOL { \
12 listpool pool; \
13 list nodes[NUM_BLOCKS]; \
14 TYPE blocks[NUM_BLOCKS]; \
15 } POOL;
16
17/// Creates a new listpool.
18#define listpool_make(POOL) \
19 { \
20 assert(POOL); \
21 const size_t block_size = sizeof((POOL)->blocks[0]); \
22 const size_t num_blocks = sizeof((POOL)->blocks) / block_size; \
23 listpool_make_(&(POOL)->pool, (POOL)->nodes, (POOL)->blocks, num_blocks, \
24 block_size); \
25 }
26
27/// Allocate a new block.
28/// Return 0 if there is no memory left.
29#define listpool_alloc(POOL) listpool_alloc_(&(POOL)->pool)
30
31/// Free the block.
32/// The block pointer is conveniently set to 0.
33#define listpool_free(POOL, ITEM) listpool_free_(&(POOL)->pool, (void**)ITEM)
34
35/// Remove a value from the list.
36/// Defined here instead of DEF_LISTPOOL_IMPL() because not all types may have
37/// an operator==.
38#define listpool_remove(POOL, VAL) \
39 { \
40 listpool_foreach(POOL, iter, { \
41 if (*iter == VAL) { \
42 listpool_free(POOL, &iter); \
43 break; \
44 } \
45 }); \
46 }
47
48/// Iterate over the used items of the pool.
49#define listpool_foreach(POOL, ITER, BODY) \
50 for (list* it_ = (POOL)->pool.used; it_; it_ = it_->next) { \
51 typeof((POOL)->blocks[0])* ITER = \
52 &(POOL)->blocks[it_ - (POOL)->pool.nodes]; \
53 (void)ITER; \
54 BODY; \
55 }
56
57typedef struct listpool {
58 size_t block_size_bytes;
59 size_t num_blocks;
60 list* free; // Head of the free list.
61 list* used; // Head of the used list.
62 list* nodes; // Array of nodes.
63 uint8_t* blocks; // Array of blocks;
64} listpool;
65
66/// Create a new list pool from a user-provided array of memory.
67/// `nodes` must have at least `num_blocks` nodes.
68/// `blocks` must be at least `num_blocks` * `block_size_bytes` bytes.
69/// All blocks are zeroed out for convenience.
70void listpool_make_(listpool* pool, list* nodes, void* blocks,
71 size_t num_blocks, size_t block_size_bytes);
72
73/// Allocate a new block.
74/// Return 0 if there is no memory left.
75void* listpool_alloc_(listpool* pool);
76
77/// Free the block.
78/// The block pointer is conveniently set to 0.
79void listpool_free_(listpool* pool, void** block_ptr);
diff --git a/listpool/src/listpool.c b/listpool/src/listpool.c
new file mode 100644
index 0000000..9c86a3b
--- /dev/null
+++ b/listpool/src/listpool.c
@@ -0,0 +1,78 @@
1#include "listpool.h"
2
3#include <string.h>
4
5void listpool_make_(listpool* pool, list* nodes, void* blocks,
6 size_t num_blocks, size_t block_size_bytes) {
7 assert(pool);
8 pool->block_size_bytes = block_size_bytes;
9 pool->num_blocks = num_blocks;
10 pool->free = &nodes[0];
11 pool->used = 0;
12 pool->nodes = nodes;
13 pool->blocks = blocks;
14 list_make(nodes, num_blocks);
15 memset(blocks, 0, num_blocks * block_size_bytes);
16}
17
18void* listpool_alloc_(listpool* pool) {
19 assert(pool);
20 if (!pool->free) {
21 return 0;
22 }
23
24 const size_t index = pool->free - pool->nodes;
25 assert(index < pool->num_blocks);
26
27 list* free = pool->free;
28 pool->free = pool->free->next;
29
30 // pool->used is always the head of the used list, so prepend the new item to
31 // the list.
32 list* new_used = free;
33 new_used->prev = 0;
34 new_used->next = pool->used;
35 if (pool->used) {
36 pool->used->prev = new_used;
37 }
38 pool->used = new_used;
39
40 return pool->blocks + index * pool->block_size_bytes;
41}
42
43void listpool_free_(listpool* pool, void** block_ptr) {
44 assert(pool);
45 assert(block_ptr);
46
47 memset(*block_ptr, 0, pool->block_size_bytes);
48
49 const size_t index =
50 ((uint8_t*)*block_ptr - pool->blocks) / pool->block_size_bytes;
51 assert(index < pool->num_blocks);
52
53 list* item = &pool->nodes[index];
54
55 // We must remove the item from the used list first.
56 if (item->prev) {
57 item->prev->next = item->next;
58 }
59 if (item->next) {
60 item->next->prev = item->prev;
61 }
62 if (item == pool->used) {
63 pool->used = item->next;
64 }
65
66 // pool->free is always the head of the free list, so prepend the new item to
67 // the list. The item is now free to wire after removing it from the used
68 // list.
69 if (!pool->free) {
70 pool->free = item;
71 } else {
72 item->next = pool->free;
73 pool->free->prev = item;
74 pool->free = item;
75 }
76
77 *block_ptr = 0;
78}
diff --git a/listpool/test/listpool_test.c b/listpool/test/listpool_test.c
new file mode 100644
index 0000000..cb54d00
--- /dev/null
+++ b/listpool/test/listpool_test.c
@@ -0,0 +1,166 @@
1#include "listpool.h"
2
3#include "test.h"
4
5#define NUM_BLOCKS 10
6
7DEF_LISTPOOL(test_pool, int, NUM_BLOCKS);
8
9static int count(test_pool* pool) {
10 int count = 0;
11 listpool_foreach(pool, n, { count++; });
12 return count;
13}
14
15static int sum(test_pool* pool) {
16 int sum = 0;
17 listpool_foreach(pool, n, { sum += *n; });
18 return sum;
19}
20
21// Create a pool.
22TEST_CASE(listpool_create) {
23 test_pool pool;
24 listpool_make(&pool);
25}
26
27// Allocate all N blocks.
28TEST_CASE(listpool_fully_allocate) {
29 test_pool pool;
30 listpool_make(&pool);
31
32 for (int i = 0; i < NUM_BLOCKS; ++i) {
33 const int* block = listpool_alloc(&pool);
34 TEST_TRUE(block != 0);
35 }
36}
37
38// Allocate all N blocks, then free them.
39TEST_CASE(listpool_fill_then_free) {
40 test_pool pool;
41 listpool_make(&pool);
42
43 int* blocks[NUM_BLOCKS] = {0};
44 for (int i = 0; i < NUM_BLOCKS; i++) {
45 blocks[i] = listpool_alloc(&pool);
46 TEST_TRUE(blocks[i] != 0);
47 }
48
49 for (int i = 0; i < NUM_BLOCKS; i++) {
50 listpool_free(&pool, &blocks[i]);
51 TEST_EQUAL(blocks[i], 0); // Pointer should be set to 0 on free.
52 }
53
54 TEST_EQUAL(count(&pool), 0);
55}
56
57// Attempt to allocate blocks past the maximum pool size.
58// The pool should handle the failed allocations gracefully.
59TEST_CASE(listpool_allocate_beyond_max_size) {
60 test_pool pool;
61 listpool_make(&pool);
62
63 // Fully allocate the pool.
64 for (int i = 0; i < NUM_BLOCKS; ++i) {
65 TEST_TRUE(listpool_alloc(&pool) != 0);
66 }
67
68 // Past the end.
69 for (int i = 0; i < NUM_BLOCKS; ++i) {
70 TEST_EQUAL(listpool_alloc(&pool), 0);
71 }
72}
73
74// Free blocks should always remain zeroed out.
75// This tests the invariant right after creating the pool.
76TEST_CASE(listpool_zero_free_blocks_after_creation) {
77 test_pool pool;
78 listpool_make(&pool);
79
80 const int zero = 0;
81 for (int i = 0; i < NUM_BLOCKS; ++i) {
82 const int* block = (const int*)(pool.blocks) + i;
83 TEST_EQUAL(memcmp(block, &zero, sizeof(int)), 0);
84 }
85}
86
87// Free blocks should always remain zeroed out.
88// This tests the invariant after freeing a block.
89TEST_CASE(listpool_zero_free_block_after_free) {
90 test_pool pool;
91 listpool_make(&pool);
92
93 int* val = listpool_alloc(&pool);
94 TEST_TRUE(val != 0);
95 *val = 177;
96
97 int* old_val = val;
98 listpool_free(&pool, &val); // val pointer is set to 0.
99 TEST_EQUAL(*old_val, 0); // Block is zeroed out after free.
100}
101
102// Traverse an empty pool.
103TEST_CASE(listpool_traverse_empty) {
104 test_pool pool;
105 listpool_make(&pool);
106
107 TEST_EQUAL(count(&pool), 0);
108}
109
110// Traverse a partially full pool.
111TEST_CASE(listpool_traverse_partially_full) {
112 const int N = NUM_BLOCKS / 2;
113
114 test_pool pool;
115 listpool_make(&pool);
116
117 for (int i = 0; i < N; ++i) {
118 int* val = listpool_alloc(&pool);
119 TEST_TRUE(val != 0);
120 *val = i + 1;
121 }
122
123 TEST_EQUAL(sum(&pool), (N) * (N + 1) / 2);
124}
125
126// Traverse a full pool.
127TEST_CASE(listpool_traverse_full) {
128 test_pool pool;
129 listpool_make(&pool);
130
131 for (int i = 0; i < NUM_BLOCKS; ++i) {
132 int* val = listpool_alloc(&pool);
133 TEST_TRUE(val != 0);
134 *val = i + 1;
135 }
136
137 TEST_EQUAL(sum(&pool), (NUM_BLOCKS) * (NUM_BLOCKS + 1) / 2);
138}
139
140// Remove a value from the list.
141TEST_CASE(listpool_remove_value) {
142 test_pool pool;
143 listpool_make(&pool);
144
145 int* x = listpool_alloc(&pool);
146 int* y = listpool_alloc(&pool);
147 TEST_TRUE(x != 0);
148 TEST_TRUE(y != 0);
149
150 *x = 155;
151 *y = 177;
152
153 listpool_remove(&pool, 155); // x
154
155 TEST_EQUAL(count(&pool), 1);
156 TEST_EQUAL(sum(&pool), *y);
157}
158
159// Stress test.
160//
161// 1. Allocate the pool, either fully or partially. If fully, attempt to
162// allocate some items past the end.
163//
164// 2. Free all allocated items in some random order.
165
166int main() { return 0; }
diff --git a/listpool/test/test.h b/listpool/test/test.h
new file mode 100644
index 0000000..fd8dc22
--- /dev/null
+++ b/listpool/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
diff --git a/log/CMakeLists.txt b/log/CMakeLists.txt
new file mode 100644
index 0000000..490c78c
--- /dev/null
+++ b/log/CMakeLists.txt
@@ -0,0 +1,9 @@
1cmake_minimum_required(VERSION 3.16)
2
3add_library(log
4 src/log.c)
5
6target_include_directories(log PUBLIC
7 include)
8
9target_compile_options(log PRIVATE -Wall -Wextra)
diff --git a/log/README.md b/log/README.md
new file mode 100644
index 0000000..1c70225
--- /dev/null
+++ b/log/README.md
@@ -0,0 +1 @@
# Logging Library
diff --git a/log/include/log/log.h b/log/include/log/log.h
new file mode 100644
index 0000000..41a83cc
--- /dev/null
+++ b/log/include/log/log.h
@@ -0,0 +1,19 @@
1#pragma once
2
3// Current implementation assumes a posix environment.
4
5#include <stdio.h>
6
7typedef enum { LogDebug, LogInfo, LogWarning, LogError } LogLevel;
8
9#define LOG(tag, ...) \
10 { \
11 printf("[%s] %s:%d: ", #tag, __FILE__, __LINE__); \
12 printf(__VA_ARGS__); \
13 printf("\n"); \
14 }
15
16#define LOGD(...) LOG(DEBUG, __VA_ARGS__)
17#define LOGI(...) LOG(INFO, __VA_ARGS__)
18#define LOGW(...) LOG(WARN, __VA_ARGS__)
19#define LOGE(...) LOG(ERROR, __VA_ARGS__)
diff --git a/log/src/log.c b/log/src/log.c
new file mode 100644
index 0000000..0e0248e
--- /dev/null
+++ b/log/src/log.c
@@ -0,0 +1 @@
#include <log/log.h>
diff --git a/mempool/CMakeLists.txt b/mempool/CMakeLists.txt
new file mode 100644
index 0000000..1ff4ff1
--- /dev/null
+++ b/mempool/CMakeLists.txt
@@ -0,0 +1,23 @@
1cmake_minimum_required(VERSION 3.16)
2
3project(mempool)
4
5# Library
6
7add_library(mempool
8 src/mempool.c)
9
10target_include_directories(mempool PUBLIC
11 include)
12
13target_compile_options(mempool PRIVATE -Wall -Wextra)
14
15# Test
16
17add_executable(mempool_test
18 test/mempool_test.c)
19
20target_link_libraries(mempool_test
21 mempool)
22
23target_compile_options(mempool_test PRIVATE -DUNIT_TEST -DNDEBUG -Wall -Wextra)
diff --git a/mempool/README.md b/mempool/README.md
new file mode 100644
index 0000000..ed2935e
--- /dev/null
+++ b/mempool/README.md
@@ -0,0 +1,20 @@
1# Mempool
2
3A memory pool implementation.
4
5Each block in the pool is tagged with a boolean value that indicates whether the
6block is free or in use. The allocator otherwise maintains little additional
7information. Therefore, some operations have linear-time complexity.
8Specifically:
9
10- Allocating a block scans the pool for the next free block in linear time.
11- Freeing a block runs in constant time.
12- Iterating over the pool's used blocks is linear over the number of blocks in
13 the pool, as opposed to just the number of used blocks.
14
15The allocator's internal data is also stored separately from the main array of
16blocks so that the block data remains tightly packed.
17
18For convenience of programming and debugging, free blocks in the mempool always
19remain zeroed out. If your data structures are valid when zeroed out, then you
20do not need to explicitly initialize them.
diff --git a/mempool/include/mempool.h b/mempool/include/mempool.h
new file mode 100644
index 0000000..41d56e5
--- /dev/null
+++ b/mempool/include/mempool.h
@@ -0,0 +1,91 @@
1#pragma once
2
3#include <assert.h>
4#include <stdbool.h>
5#include <stddef.h>
6#include <stdint.h>
7
8/// Define a typed mempool of the given number of blocks.
9#define DEF_MEMPOOL(POOL, TYPE, NUM_BLOCKS) \
10 typedef struct POOL { \
11 mempool pool; \
12 BlockInfo block_info[NUM_BLOCKS]; \
13 TYPE blocks[NUM_BLOCKS]; \
14 } POOL;
15
16/// Create a new pool.
17#define mempool_make(POOL) \
18 { \
19 assert(POOL); \
20 const size_t block_size = sizeof((POOL)->blocks[0]); \
21 const size_t num_blocks = sizeof((POOL)->blocks) / block_size; \
22 mempool_make_(&(POOL)->pool, (POOL)->block_info, (POOL)->blocks, \
23 num_blocks, block_size); \
24 }
25
26/// Allocate a new block.
27/// Return 0 if there is no memory left.
28#define mempool_alloc(POOL) mempool_alloc_(&(POOL)->pool)
29
30/// Free the block.
31/// The block pointer is conveniently set to 0.
32#define mempool_free(POOL, BLOCK_PTR) \
33 assert(*BLOCK_PTR); \
34 mempool_free_(&(POOL)->pool, (void**)BLOCK_PTR)
35
36/// Return the ith block.
37/// The block must have been allocated.
38#define mempool_get_block(POOL, INDEX) \
39 ((typeof((POOL)->blocks[0])*)mempool_get_block_(&(POOL)->pool, INDEX))
40
41/// Get the index to the given block.
42#define mempool_get_block_index(POOL, BLOCK_PTR) \
43 mempool_get_block_index_(&(POOL)->pool, BLOCK_PTR)
44
45/// Iterate over the used blocks of the pool.
46#define mempool_foreach(POOL, ITER, BODY) \
47 for (size_t i = 0; \
48 i < (sizeof((POOL)->blocks) / sizeof(typeof((POOL)->blocks[0]))); \
49 ++i) { \
50 if (!(POOL)->block_info[i].used) { \
51 continue; \
52 } \
53 typeof((POOL)->blocks[0])* ITER = &(POOL)->blocks[i]; \
54 (void)ITER; \
55 BODY; \
56 }
57
58typedef struct BlockInfo {
59 bool used;
60} BlockInfo;
61
62typedef struct mempool {
63 size_t block_size_bytes;
64 size_t num_blocks;
65 size_t next_free_block;
66 bool full;
67 BlockInfo* block_info;
68 uint8_t* blocks;
69} mempool;
70
71/// Create a pool allocator from user-provided memory.
72/// `BlockInfo` must hold at least `num_blocks` entries.
73/// `blocks` must be at least `num_blocks` * `block_size_bytes` bytes.
74/// All blocks are zeroed out for convenience.
75void mempool_make_(mempool*, BlockInfo*, void* blocks, size_t num_blocks,
76 size_t block_size_bytes);
77
78/// Allocate a new block.
79/// Return 0 if there is no memory left.
80void* mempool_alloc_(mempool*);
81
82/// Free the block.
83/// The block pointer is conveniently set to 0.
84void mempool_free_(mempool*, void** block_ptr);
85
86/// Return the ith block.
87/// The block must have been allocated.
88void* mempool_get_block_(const mempool*, size_t block_index);
89
90/// Get the index to the given block.
91size_t mempool_get_block_index_(const mempool*, const void* block);
diff --git a/mempool/src/mempool.c b/mempool/src/mempool.c
new file mode 100644
index 0000000..b7e8705
--- /dev/null
+++ b/mempool/src/mempool.c
@@ -0,0 +1,85 @@
1#include "mempool.h"
2
3#include <string.h>
4
5static inline size_t min(size_t a, size_t b) { return a < b ? a : b; }
6
7void mempool_make_(mempool* pool, BlockInfo* block_info, void* blocks,
8 size_t num_blocks, size_t block_size_bytes) {
9 assert(pool);
10 assert(block_info);
11 assert(blocks);
12 assert(num_blocks >= 1);
13 pool->block_size_bytes = block_size_bytes;
14 pool->num_blocks = num_blocks;
15 pool->next_free_block = 0;
16 pool->full = false;
17 pool->block_info = block_info;
18 pool->blocks = blocks;
19 memset(blocks, 0, num_blocks * block_size_bytes);
20}
21
22void* mempool_alloc_(mempool* pool) {
23 assert(pool);
24
25 if (pool->full) {
26 return 0;
27 }
28
29 // Allocate the block.
30 void* block = &pool->blocks[pool->next_free_block * pool->block_size_bytes];
31 pool->block_info[pool->next_free_block].used = true;
32
33 // Search for the next free block. If it does not exist, flag the pool full.
34 pool->full = true;
35 for (size_t i = 1; i < pool->num_blocks && i != 0; i++) {
36 pool->next_free_block = (pool->next_free_block + 1) % pool->num_blocks;
37 if (!pool->block_info[pool->next_free_block].used) {
38 pool->full = false;
39 break;
40 }
41 }
42
43 return block;
44}
45
46void mempool_free_(mempool* pool, void** block_ptr) {
47 assert(pool);
48 assert(block_ptr);
49
50 memset(*block_ptr, 0, pool->block_size_bytes);
51
52 const size_t block_index =
53 ((uint8_t*)*block_ptr - pool->blocks) / pool->block_size_bytes;
54 assert(block_index < pool->num_blocks);
55
56 // Disallow double-frees.
57 assert(pool->block_info[block_index].used);
58
59 pool->block_info[block_index].used = false;
60 if (pool->full) {
61 pool->next_free_block = block_index;
62 pool->full = false;
63 } else {
64 // Prefer to allocate blocks towards the start of the pool. This way, blocks
65 // should cluster around this area and the pool should offer better memory
66 // locality for used blocks.
67 pool->next_free_block = min(pool->next_free_block, block_index);
68 }
69
70 *block_ptr = 0;
71}
72
73void* mempool_get_block_(const mempool* pool, size_t block_index) {
74 assert(pool);
75 assert(block_index < pool->num_blocks);
76 assert(pool->block_info[block_index].used);
77 return pool->blocks + block_index * pool->block_size_bytes;
78}
79
80size_t mempool_get_block_index_(const mempool* pool, const void* block) {
81 assert(pool);
82 const size_t block_byte_index = (const uint8_t*)block - pool->blocks;
83 assert(block_byte_index % pool->block_size_bytes == 0);
84 return block_byte_index / pool->block_size_bytes;
85}
diff --git a/mempool/test/mempool_test.c b/mempool/test/mempool_test.c
new file mode 100644
index 0000000..d257922
--- /dev/null
+++ b/mempool/test/mempool_test.c
@@ -0,0 +1,157 @@
1#include "mempool.h"
2
3#include "test.h"
4
5#define NUM_BLOCKS 10
6
7DEF_MEMPOOL(test_pool, int, NUM_BLOCKS);
8
9static int count(test_pool* pool) {
10 int count = 0;
11 mempool_foreach(pool, n, { count++; });
12 return count;
13}
14
15static int sum(test_pool* pool) {
16 int sum = 0;
17 mempool_foreach(pool, n, { sum += *n; });
18 return sum;
19}
20
21// Create a pool.
22TEST_CASE(mempool_create) {
23 test_pool pool;
24 mempool_make(&pool);
25}
26
27// Allocate all N blocks.
28TEST_CASE(mempool_allocate_until_full) {
29 test_pool pool;
30 mempool_make(&pool);
31
32 for (int i = 0; i < NUM_BLOCKS; ++i) {
33 const int* block = mempool_alloc(&pool);
34 TEST_TRUE(block != 0);
35 }
36}
37
38// Allocate all N blocks, then free them.
39TEST_CASE(mempool_fill_then_free) {
40 test_pool pool;
41 mempool_make(&pool);
42
43 int* blocks[NUM_BLOCKS] = {0};
44 for (int i = 0; i < NUM_BLOCKS; ++i) {
45 blocks[i] = mempool_alloc(&pool);
46 TEST_TRUE(blocks[i] != 0);
47 }
48
49 for (int i = 0; i < NUM_BLOCKS; ++i) {
50 mempool_free(&pool, &blocks[i]);
51 TEST_EQUAL(blocks[i], 0); // Pointer should be set to 0 on free.
52 }
53
54 TEST_EQUAL(count(&pool), 0);
55}
56
57// Attempt to allocate blocks past the maximum pool size.
58// The pool should handle the failed allocations gracefully.
59TEST_CASE(mempool_allocate_beyond_max_size) {
60 test_pool pool;
61 mempool_make(&pool);
62
63 // Fully allocate the pool.
64 for (int i = 0; i < NUM_BLOCKS; ++i) {
65 TEST_TRUE(mempool_alloc(&pool) != 0);
66 }
67
68 // Past the end.
69 for (int i = 0; i < NUM_BLOCKS; ++i) {
70 TEST_EQUAL(mempool_alloc(&pool), 0);
71 }
72}
73
74// Free blocks should always remain zeroed out.
75// This tests the invariant right after creating the pool.
76TEST_CASE(mempool_zero_free_blocks_after_creation) {
77 test_pool pool;
78 mempool_make(&pool);
79
80 const int zero = 0;
81 for (int i = 0; i < NUM_BLOCKS; ++i) {
82 const int* block = (const int*)(pool.blocks) + i;
83 TEST_EQUAL(memcmp(block, &zero, sizeof(int)), 0);
84 }
85}
86
87// Free blocks should always remain zeroed out.
88// This tests the invariant after freeing a block.
89TEST_CASE(mempool_zero_free_block_after_free) {
90 test_pool pool;
91 mempool_make(&pool);
92
93 int* val = mempool_alloc(&pool);
94 TEST_TRUE(val != 0);
95 *val = 177;
96
97 int* old_val = val;
98 mempool_free(&pool, &val); // val pointer is set to 0.
99 TEST_EQUAL(*old_val, 0); // Block is zeroed out after free.
100}
101
102// Traverse an empty pool.
103TEST_CASE(mempool_traverse_empty) {
104 test_pool pool;
105 mempool_make(&pool);
106
107 TEST_EQUAL(count(&pool), 0);
108}
109
110// Traverse a partially full pool.
111TEST_CASE(mempool_traverse_partially_full) {
112 const int N = NUM_BLOCKS / 2;
113
114 test_pool pool;
115 mempool_make(&pool);
116
117 for (int i = 0; i < N; ++i) {
118 int* val = mempool_alloc(&pool);
119 TEST_TRUE(val != 0);
120 *val = i + 1;
121 }
122
123 TEST_EQUAL(sum(&pool), N * (N + 1) / 2);
124}
125
126// Traverse a full pool.
127TEST_CASE(mempool_traverse_full) {
128 test_pool pool;
129 mempool_make(&pool);
130
131 for (int i = 0; i < NUM_BLOCKS; ++i) {
132 int* val = mempool_alloc(&pool);
133 TEST_TRUE(val != 0);
134 *val = i + 1;
135 }
136
137 TEST_EQUAL(sum(&pool), NUM_BLOCKS * (NUM_BLOCKS + 1) / 2);
138}
139
140// Get the ith (allocated) block.
141TEST_CASE(mempool_get_block) {
142 test_pool pool;
143 mempool_make(&pool);
144
145 for (int i = 0; i < NUM_BLOCKS; ++i) {
146 int* block = mempool_alloc(&pool);
147 TEST_TRUE(block != 0);
148 *block = i;
149 TEST_EQUAL(mempool_get_block_index(&pool, block), (size_t)i);
150 }
151
152 for (int i = 0; i < NUM_BLOCKS; ++i) {
153 TEST_EQUAL(*mempool_get_block(&pool, i), i);
154 }
155}
156
157int main() { return 0; }
diff --git a/mempool/test/test.h b/mempool/test/test.h
new file mode 100644
index 0000000..fd8dc22
--- /dev/null
+++ b/mempool/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