aboutsummaryrefslogtreecommitdiff
path: root/list
diff options
context:
space:
mode:
Diffstat (limited to 'list')
-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
5 files changed, 277 insertions, 0 deletions
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