@velox0/cerver 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +35 -0
- package/.github/workflows/publish.yml +3 -0
- package/Makefile +26 -0
- package/bin/cerver.js +11 -0
- package/lib/commands/dev.js +23 -9
- package/lib/commands/new.js +273 -79
- package/lib/ir/transform.js +14 -20
- package/package.json +2 -2
- package/runtime/cerver.h +46 -36
- package/runtime/http_parser.c +12 -11
- package/runtime/http_writer.c +24 -24
- package/runtime/mime.c +1 -0
- package/runtime/router.c +2 -1
- package/runtime/server.c +56 -55
- package/runtime/static.c +11 -11
- package/runtime/tests/minunit.c +76 -0
- package/runtime/tests/minunit.h +64 -0
- package/runtime/tests/runtime_tests.c +414 -0
package/runtime/server.c
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
#include <fcntl.h>
|
|
21
21
|
#include <time.h>
|
|
22
22
|
#include <sys/mman.h>
|
|
23
|
+
#include <sys/time.h>
|
|
23
24
|
#include <sys/socket.h>
|
|
24
25
|
#include <sys/types.h>
|
|
25
26
|
#include <netinet/in.h>
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
static void* cerver_memmem(const void* hay, size_t haylen, const void* needle, size_t nlen) {
|
|
56
57
|
if (nlen == 0) return (void*)hay;
|
|
57
58
|
if (nlen > haylen) return NULL;
|
|
58
|
-
const char* p
|
|
59
|
+
const char* p = (const char*)hay;
|
|
59
60
|
const char* end = p + haylen - nlen;
|
|
60
61
|
for (; p <= end; p++) {
|
|
61
62
|
if (memcmp(p, needle, nlen) == 0) return (void*)p;
|
|
@@ -91,12 +92,12 @@ static int get_cpu_count(void) {
|
|
|
91
92
|
/* ------------------------------------------------------------------ */
|
|
92
93
|
|
|
93
94
|
typedef struct {
|
|
94
|
-
int
|
|
95
|
-
int
|
|
96
|
-
int
|
|
97
|
-
int
|
|
95
|
+
int fds[CERVER_CONN_QUEUE_SIZE];
|
|
96
|
+
int head;
|
|
97
|
+
int tail;
|
|
98
|
+
int count;
|
|
98
99
|
pthread_mutex_t lock;
|
|
99
|
-
pthread_cond_t
|
|
100
|
+
pthread_cond_t not_empty;
|
|
100
101
|
} conn_queue_t;
|
|
101
102
|
|
|
102
103
|
static void cq_init(conn_queue_t* q) {
|
|
@@ -119,7 +120,7 @@ static int cq_push(conn_queue_t* q, int fd) {
|
|
|
119
120
|
return -1;
|
|
120
121
|
}
|
|
121
122
|
q->fds[q->tail] = fd;
|
|
122
|
-
q->tail
|
|
123
|
+
q->tail = (q->tail + 1) % CERVER_CONN_QUEUE_SIZE;
|
|
123
124
|
q->count++;
|
|
124
125
|
pthread_cond_signal(&q->not_empty);
|
|
125
126
|
pthread_mutex_unlock(&q->lock);
|
|
@@ -140,7 +141,7 @@ static int cq_pop(conn_queue_t* q, volatile int* running) {
|
|
|
140
141
|
pthread_mutex_unlock(&q->lock);
|
|
141
142
|
return -1;
|
|
142
143
|
}
|
|
143
|
-
int fd
|
|
144
|
+
int fd = q->fds[q->head];
|
|
144
145
|
q->head = (q->head + 1) % CERVER_CONN_QUEUE_SIZE;
|
|
145
146
|
q->count--;
|
|
146
147
|
pthread_mutex_unlock(&q->lock);
|
|
@@ -152,31 +153,31 @@ static conn_queue_t g_conn_queue;
|
|
|
152
153
|
|
|
153
154
|
/* Global worker readiness tracking */
|
|
154
155
|
typedef struct {
|
|
155
|
-
int
|
|
156
|
-
int
|
|
156
|
+
int workers_ready;
|
|
157
|
+
int workers_expected;
|
|
157
158
|
pthread_mutex_t lock;
|
|
158
|
-
pthread_cond_t
|
|
159
|
+
pthread_cond_t ready_cv;
|
|
159
160
|
} worker_readiness_t;
|
|
160
161
|
|
|
161
|
-
static worker_readiness_t g_worker_readiness = {.workers_ready
|
|
162
|
+
static worker_readiness_t g_worker_readiness = {.workers_ready = 0,
|
|
162
163
|
.workers_expected = 0,
|
|
163
|
-
.lock
|
|
164
|
-
.ready_cv
|
|
164
|
+
.lock = PTHREAD_MUTEX_INITIALIZER,
|
|
165
|
+
.ready_cv = PTHREAD_COND_INITIALIZER};
|
|
165
166
|
|
|
166
167
|
/* Global acceptor startup tracking */
|
|
167
168
|
typedef struct {
|
|
168
|
-
int
|
|
169
|
-
int
|
|
169
|
+
int acceptors_ready;
|
|
170
|
+
int start_accepting;
|
|
170
171
|
pthread_mutex_t lock;
|
|
171
|
-
pthread_cond_t
|
|
172
|
-
pthread_cond_t
|
|
172
|
+
pthread_cond_t ready_cv;
|
|
173
|
+
pthread_cond_t start_cv;
|
|
173
174
|
} acceptor_readiness_t;
|
|
174
175
|
|
|
175
176
|
static acceptor_readiness_t g_acceptor_readiness = {.acceptors_ready = 0,
|
|
176
177
|
.start_accepting = 0,
|
|
177
|
-
.lock
|
|
178
|
-
.ready_cv
|
|
179
|
-
.start_cv
|
|
178
|
+
.lock = PTHREAD_MUTEX_INITIALIZER,
|
|
179
|
+
.ready_cv = PTHREAD_COND_INITIALIZER,
|
|
180
|
+
.start_cv = PTHREAD_COND_INITIALIZER};
|
|
180
181
|
|
|
181
182
|
/* ------------------------------------------------------------------ */
|
|
182
183
|
/* Buffered read */
|
|
@@ -185,7 +186,7 @@ static acceptor_readiness_t g_acceptor_readiness = {.acceptors_ready = 0,
|
|
|
185
186
|
static char* read_full_request(int fd, size_t* out_len) {
|
|
186
187
|
size_t cap = CERVER_READ_BUF;
|
|
187
188
|
size_t len = 0;
|
|
188
|
-
char*
|
|
189
|
+
char* buf = malloc(cap + 1);
|
|
189
190
|
if (!buf) return NULL;
|
|
190
191
|
|
|
191
192
|
while (len < (size_t)CERVER_READ_BUF_MAX) {
|
|
@@ -227,16 +228,16 @@ static void handle_connection(cerver_server_t* srv, int client_fd) {
|
|
|
227
228
|
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
|
|
228
229
|
|
|
229
230
|
int request_count = 0;
|
|
230
|
-
int keepalive
|
|
231
|
+
int keepalive = 1;
|
|
231
232
|
|
|
232
233
|
while (keepalive && srv->running && request_count < CERVER_KEEPALIVE_MAX) {
|
|
233
234
|
struct timeval tv;
|
|
234
|
-
tv.tv_sec
|
|
235
|
+
tv.tv_sec = (request_count == 0) ? 5 : CERVER_KEEPALIVE_TIMEOUT;
|
|
235
236
|
tv.tv_usec = 0;
|
|
236
237
|
setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
237
238
|
|
|
238
239
|
size_t req_len = 0;
|
|
239
|
-
char*
|
|
240
|
+
char* buf = read_full_request(client_fd, &req_len);
|
|
240
241
|
if (!buf || req_len == 0) {
|
|
241
242
|
if (buf) free(buf);
|
|
242
243
|
break;
|
|
@@ -355,9 +356,9 @@ static int create_listener(int port, int reuseport) {
|
|
|
355
356
|
|
|
356
357
|
struct sockaddr_in addr;
|
|
357
358
|
memset(&addr, 0, sizeof(addr));
|
|
358
|
-
addr.sin_family
|
|
359
|
+
addr.sin_family = AF_INET;
|
|
359
360
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
360
|
-
addr.sin_port
|
|
361
|
+
addr.sin_port = htons((uint16_t)port);
|
|
361
362
|
|
|
362
363
|
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
|
363
364
|
perror("cerver: bind");
|
|
@@ -380,7 +381,7 @@ static int create_listener(int port, int reuseport) {
|
|
|
380
381
|
|
|
381
382
|
static int accept_connection(int listen_fd) {
|
|
382
383
|
struct sockaddr_in ca;
|
|
383
|
-
socklen_t
|
|
384
|
+
socklen_t cl = sizeof(ca);
|
|
384
385
|
#ifdef __linux__
|
|
385
386
|
return accept4(listen_fd, (struct sockaddr*)&ca, &cl, SOCK_CLOEXEC);
|
|
386
387
|
#else
|
|
@@ -394,7 +395,7 @@ static int accept_connection(int listen_fd) {
|
|
|
394
395
|
|
|
395
396
|
#if CERVER_USE_KQUEUE
|
|
396
397
|
static void* acceptor_loop(void* arg) {
|
|
397
|
-
cerver_worker_t* w
|
|
398
|
+
cerver_worker_t* w = (cerver_worker_t*)arg;
|
|
398
399
|
cerver_server_t* srv = w->srv;
|
|
399
400
|
|
|
400
401
|
int kq = kqueue();
|
|
@@ -419,8 +420,8 @@ static void* acceptor_loop(void* arg) {
|
|
|
419
420
|
pthread_mutex_unlock(&g_acceptor_readiness.lock);
|
|
420
421
|
|
|
421
422
|
while (srv->running) {
|
|
422
|
-
struct timespec ts
|
|
423
|
-
int
|
|
423
|
+
struct timespec ts = {1, 0};
|
|
424
|
+
int nev = kevent(kq, NULL, 0, events, CERVER_MAX_EVENTS, &ts);
|
|
424
425
|
if (nev < 0) {
|
|
425
426
|
if (errno == EINTR) continue;
|
|
426
427
|
break;
|
|
@@ -454,7 +455,7 @@ static void* acceptor_loop(void* arg) {
|
|
|
454
455
|
|
|
455
456
|
#elif CERVER_USE_EPOLL
|
|
456
457
|
static void* acceptor_loop(void* arg) {
|
|
457
|
-
cerver_worker_t* w
|
|
458
|
+
cerver_worker_t* w = (cerver_worker_t*)arg;
|
|
458
459
|
cerver_server_t* srv = w->srv;
|
|
459
460
|
|
|
460
461
|
int ep = epoll_create1(EPOLL_CLOEXEC);
|
|
@@ -511,7 +512,7 @@ static void* acceptor_loop(void* arg) {
|
|
|
511
512
|
|
|
512
513
|
#else /* SELECT */
|
|
513
514
|
static void* acceptor_loop(void* arg) {
|
|
514
|
-
cerver_worker_t* w
|
|
515
|
+
cerver_worker_t* w = (cerver_worker_t*)arg;
|
|
515
516
|
cerver_server_t* srv = w->srv;
|
|
516
517
|
|
|
517
518
|
pthread_mutex_lock(&g_acceptor_readiness.lock);
|
|
@@ -526,8 +527,8 @@ static void* acceptor_loop(void* arg) {
|
|
|
526
527
|
fd_set rfds;
|
|
527
528
|
FD_ZERO(&rfds);
|
|
528
529
|
FD_SET(w->listen_fd, &rfds);
|
|
529
|
-
struct timeval tv
|
|
530
|
-
int
|
|
530
|
+
struct timeval tv = {1, 0};
|
|
531
|
+
int ret = select(w->listen_fd + 1, &rfds, NULL, NULL, &tv);
|
|
531
532
|
if (ret < 0) {
|
|
532
533
|
if (errno == EINTR) continue;
|
|
533
534
|
break;
|
|
@@ -577,7 +578,7 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
|
|
|
577
578
|
time_t mtime) {
|
|
578
579
|
time_t now = time(NULL);
|
|
579
580
|
pthread_mutex_lock(&cache->lock);
|
|
580
|
-
int
|
|
581
|
+
int best = 0;
|
|
581
582
|
time_t oldest = cache->entries[0].cached_at;
|
|
582
583
|
for (int i = 0; i < CERVER_STAT_CACHE_SIZE; i++) {
|
|
583
584
|
cerver_stat_entry_t* e = &cache->entries[i];
|
|
@@ -587,16 +588,16 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
|
|
|
587
588
|
}
|
|
588
589
|
if (e->cached_at < oldest) {
|
|
589
590
|
oldest = e->cached_at;
|
|
590
|
-
best
|
|
591
|
+
best = i;
|
|
591
592
|
}
|
|
592
593
|
}
|
|
593
594
|
cerver_stat_entry_t* slot = &cache->entries[best];
|
|
594
595
|
strncpy(slot->path, path, sizeof(slot->path) - 1);
|
|
595
596
|
slot->path[sizeof(slot->path) - 1] = '\0';
|
|
596
|
-
slot->file_size
|
|
597
|
-
slot->mtime
|
|
598
|
-
slot->cached_at
|
|
599
|
-
slot->valid
|
|
597
|
+
slot->file_size = file_size;
|
|
598
|
+
slot->mtime = mtime;
|
|
599
|
+
slot->cached_at = now;
|
|
600
|
+
slot->valid = 1;
|
|
600
601
|
pthread_mutex_unlock(&cache->lock);
|
|
601
602
|
}
|
|
602
603
|
|
|
@@ -606,25 +607,25 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
|
|
|
606
607
|
|
|
607
608
|
int cerver_init(cerver_server_t* srv, int port, int threads) {
|
|
608
609
|
memset(srv, 0, sizeof(*srv));
|
|
609
|
-
srv->port
|
|
610
|
-
srv->sock_fd
|
|
611
|
-
srv->running
|
|
612
|
-
srv->public_dir
|
|
610
|
+
srv->port = port;
|
|
611
|
+
srv->sock_fd = -1;
|
|
612
|
+
srv->running = 0;
|
|
613
|
+
srv->public_dir = NULL;
|
|
613
614
|
srv->dispatch_override = NULL;
|
|
614
|
-
srv->worker_count
|
|
615
|
-
srv->workers
|
|
615
|
+
srv->worker_count = (threads > 0) ? threads : get_cpu_count();
|
|
616
|
+
srv->workers = NULL;
|
|
616
617
|
cerver_stat_cache_init(&srv->stat_cache);
|
|
617
618
|
return 0;
|
|
618
619
|
}
|
|
619
620
|
|
|
620
621
|
int cerver_add_routes(cerver_server_t* srv, cerver_route_t* routes, int count) {
|
|
621
|
-
srv->routes
|
|
622
|
+
srv->routes = routes;
|
|
622
623
|
srv->route_count = count;
|
|
623
624
|
return 0;
|
|
624
625
|
}
|
|
625
626
|
|
|
626
627
|
int cerver_set_assets(cerver_server_t* srv, cerver_asset_t* assets, int count) {
|
|
627
|
-
srv->assets
|
|
628
|
+
srv->assets = assets;
|
|
628
629
|
srv->asset_count = count;
|
|
629
630
|
return 0;
|
|
630
631
|
}
|
|
@@ -646,7 +647,7 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
646
647
|
/* Determine pool and acceptor counts.
|
|
647
648
|
* Acceptors: min(worker_count, cpu_count) — one per core for accept.
|
|
648
649
|
* Pool workers: worker_count * 16 — enough to cover concurrent keep-alive. */
|
|
649
|
-
int cpu_count
|
|
650
|
+
int cpu_count = get_cpu_count();
|
|
650
651
|
int acceptor_count = cpu_count;
|
|
651
652
|
if (acceptor_count > srv->worker_count) acceptor_count = srv->worker_count;
|
|
652
653
|
if (acceptor_count < 1) acceptor_count = 1;
|
|
@@ -660,7 +661,7 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
660
661
|
|
|
661
662
|
/* Initialize worker readiness tracking */
|
|
662
663
|
pthread_mutex_lock(&g_worker_readiness.lock);
|
|
663
|
-
g_worker_readiness.workers_ready
|
|
664
|
+
g_worker_readiness.workers_ready = 0;
|
|
664
665
|
g_worker_readiness.workers_expected = pool_size;
|
|
665
666
|
pthread_mutex_unlock(&g_worker_readiness.lock);
|
|
666
667
|
|
|
@@ -711,7 +712,7 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
711
712
|
}
|
|
712
713
|
|
|
713
714
|
/* Start acceptor threads */
|
|
714
|
-
srv->workers
|
|
715
|
+
srv->workers = calloc((size_t)acceptor_count, sizeof(cerver_worker_t));
|
|
715
716
|
srv->worker_count = acceptor_count;
|
|
716
717
|
if (!srv->workers) {
|
|
717
718
|
perror("cerver: calloc acceptors");
|
|
@@ -726,9 +727,9 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
726
727
|
|
|
727
728
|
for (int i = 0; i < acceptor_count; i++) {
|
|
728
729
|
cerver_worker_t* w = &srv->workers[i];
|
|
729
|
-
w->id
|
|
730
|
-
w->srv
|
|
731
|
-
w->event_fd
|
|
730
|
+
w->id = i;
|
|
731
|
+
w->srv = srv;
|
|
732
|
+
w->event_fd = -1;
|
|
732
733
|
#ifdef __linux__
|
|
733
734
|
w->listen_fd = create_listener(srv->port, 1);
|
|
734
735
|
if (w->listen_fd < 0) w->listen_fd = srv->sock_fd;
|
package/runtime/static.c
CHANGED
|
@@ -62,7 +62,7 @@ typedef struct {
|
|
|
62
62
|
|
|
63
63
|
static encoding_prefs_t parse_accept_encoding(const cerver_request_t* req) {
|
|
64
64
|
encoding_prefs_t prefs = {0, 0};
|
|
65
|
-
const char*
|
|
65
|
+
const char* ae = cerver_req_header(req, "Accept-Encoding");
|
|
66
66
|
if (!ae) return prefs;
|
|
67
67
|
|
|
68
68
|
if (strstr(ae, "br")) prefs.accepts_br = 1;
|
|
@@ -92,7 +92,7 @@ static void add_cache_headers(cerver_response_t* res, const char* path) {
|
|
|
92
92
|
static int serve_embedded(cerver_server_t* srv, cerver_request_t* req, cerver_response_t* res) {
|
|
93
93
|
if (!srv->assets || srv->asset_count == 0) return -1;
|
|
94
94
|
|
|
95
|
-
const char*
|
|
95
|
+
const char* path = req->path;
|
|
96
96
|
const cerver_asset_t* found = NULL;
|
|
97
97
|
|
|
98
98
|
/*
|
|
@@ -112,7 +112,7 @@ static int serve_embedded(cerver_server_t* srv, cerver_request_t* req, cerver_re
|
|
|
112
112
|
|
|
113
113
|
/* Try with /index.html appended (for directory-like paths) */
|
|
114
114
|
if (!found) {
|
|
115
|
-
char
|
|
115
|
+
char index_path[CERVER_MAX_PATH];
|
|
116
116
|
size_t plen = strlen(path);
|
|
117
117
|
if (plen > 0 && path[plen - 1] == '/') {
|
|
118
118
|
snprintf(index_path, sizeof(index_path), "%sindex.html", path);
|
|
@@ -207,11 +207,11 @@ static int serve_filesystem(cerver_server_t* srv, cerver_request_t* req, cerver_
|
|
|
207
207
|
/* Advise the kernel we'll read sequentially */
|
|
208
208
|
madvise(mapped, file_size, MADV_SEQUENTIAL);
|
|
209
209
|
|
|
210
|
-
res->status
|
|
210
|
+
res->status = 200;
|
|
211
211
|
res->content_type = mime;
|
|
212
|
-
res->body
|
|
213
|
-
res->body_len
|
|
214
|
-
res->_body_owned
|
|
212
|
+
res->body = (const char*)mapped;
|
|
213
|
+
res->body_len = file_size;
|
|
214
|
+
res->_body_owned = 2; /* Special flag: needs munmap, not free */
|
|
215
215
|
} else {
|
|
216
216
|
/* Small files: read into buffer (avoids mmap overhead) */
|
|
217
217
|
int fd = open(full_path, O_RDONLY);
|
|
@@ -236,11 +236,11 @@ static int serve_filesystem(cerver_server_t* srv, cerver_request_t* req, cerver_
|
|
|
236
236
|
return -1;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
res->status
|
|
239
|
+
res->status = 200;
|
|
240
240
|
res->content_type = mime;
|
|
241
|
-
res->body
|
|
242
|
-
res->body_len
|
|
243
|
-
res->_body_owned
|
|
241
|
+
res->body = file_data;
|
|
242
|
+
res->body_len = file_size;
|
|
243
|
+
res->_body_owned = 1; /* malloc'd */
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
add_cache_headers(res, path);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#include "minunit.h"
|
|
2
|
+
|
|
3
|
+
#include <stdio.h>
|
|
4
|
+
#include <string.h>
|
|
5
|
+
#include <unistd.h>
|
|
6
|
+
|
|
7
|
+
int mu_tests_run = 0;
|
|
8
|
+
int mu_tests_failed = 0;
|
|
9
|
+
int mu_test_failed = 0;
|
|
10
|
+
|
|
11
|
+
static int mu_is_tty(FILE* f) { return isatty(fileno(f)); }
|
|
12
|
+
|
|
13
|
+
static const char* mu_color(FILE* f, const char* code) { return mu_is_tty(f) ? code : ""; }
|
|
14
|
+
|
|
15
|
+
const char* mu_snip(const char* s, char* buf, size_t cap) {
|
|
16
|
+
if (s == NULL) {
|
|
17
|
+
return "(null)";
|
|
18
|
+
}
|
|
19
|
+
if (cap == 0) {
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
size_t len = strlen(s);
|
|
24
|
+
if (len < cap) {
|
|
25
|
+
snprintf(buf, cap, "%s", s);
|
|
26
|
+
return buf;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (cap <= 4) {
|
|
30
|
+
size_t keep = cap - 1;
|
|
31
|
+
memcpy(buf, s, keep);
|
|
32
|
+
buf[keep] = '\0';
|
|
33
|
+
return buf;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
size_t keep = cap - 4;
|
|
37
|
+
memcpy(buf, s, keep);
|
|
38
|
+
memcpy(buf + keep, "...", 3);
|
|
39
|
+
buf[keep + 3] = '\0';
|
|
40
|
+
return buf;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
void mu_fail(const char* file, int line, const char* msg) {
|
|
44
|
+
const char* red = mu_color(stderr, "\x1b[31m");
|
|
45
|
+
const char* reset = mu_color(stderr, "\x1b[0m");
|
|
46
|
+
|
|
47
|
+
mu_test_failed = 1;
|
|
48
|
+
fprintf(stderr, "%sFAIL%s %s:%d: %s\n", red, reset, file, line, msg);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void mu_run(const char* name, mu_test_fn fn) {
|
|
52
|
+
mu_tests_run++;
|
|
53
|
+
mu_test_failed = 0;
|
|
54
|
+
fn();
|
|
55
|
+
if (mu_test_failed) {
|
|
56
|
+
mu_tests_failed++;
|
|
57
|
+
fprintf(stderr, " in %s\n", name);
|
|
58
|
+
} else {
|
|
59
|
+
const char* green = mu_color(stdout, "\x1b[32m");
|
|
60
|
+
const char* reset = mu_color(stdout, "\x1b[0m");
|
|
61
|
+
printf("%sok%s %s\n", green, reset, name);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
int mu_report(void) {
|
|
66
|
+
const char* reset = mu_color(stdout, "\x1b[0m");
|
|
67
|
+
if (mu_tests_failed) {
|
|
68
|
+
const char* red = mu_color(stdout, "\x1b[31m");
|
|
69
|
+
printf("%sSummary:%s %d tests, %s%d failures%s\n", red, reset, mu_tests_run, red,
|
|
70
|
+
mu_tests_failed, reset);
|
|
71
|
+
} else {
|
|
72
|
+
const char* green = mu_color(stdout, "\x1b[32m");
|
|
73
|
+
printf("%sSummary:%s %d tests, %s0 failures%s\n", green, reset, mu_tests_run, green, reset);
|
|
74
|
+
}
|
|
75
|
+
return mu_tests_failed ? 1 : 0;
|
|
76
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#ifndef CERVER_MINUNIT_H
|
|
2
|
+
#define CERVER_MINUNIT_H
|
|
3
|
+
|
|
4
|
+
#include <stddef.h>
|
|
5
|
+
#include <stdio.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
|
|
8
|
+
typedef void (*mu_test_fn)(void);
|
|
9
|
+
|
|
10
|
+
#define MU_SNIP_MAX 80
|
|
11
|
+
|
|
12
|
+
extern int mu_tests_run;
|
|
13
|
+
extern int mu_tests_failed;
|
|
14
|
+
extern int mu_test_failed;
|
|
15
|
+
|
|
16
|
+
void mu_fail(const char* file, int line, const char* msg);
|
|
17
|
+
void mu_run(const char* name, mu_test_fn fn);
|
|
18
|
+
int mu_report(void);
|
|
19
|
+
const char* mu_snip(const char* s, char* buf, size_t cap);
|
|
20
|
+
|
|
21
|
+
#define MU_ASSERT(cond) \
|
|
22
|
+
do { \
|
|
23
|
+
if (!(cond)) { \
|
|
24
|
+
mu_fail(__FILE__, __LINE__, #cond); \
|
|
25
|
+
return; \
|
|
26
|
+
} \
|
|
27
|
+
} while (0)
|
|
28
|
+
|
|
29
|
+
#define MU_ASSERT_EQ_INT(a, b) \
|
|
30
|
+
do { \
|
|
31
|
+
if ((a) != (b)) { \
|
|
32
|
+
char _mu_buf[128]; \
|
|
33
|
+
snprintf(_mu_buf, sizeof(_mu_buf), "got %d expected %d", (int)(a), (int)(b)); \
|
|
34
|
+
mu_fail(__FILE__, __LINE__, _mu_buf); \
|
|
35
|
+
return; \
|
|
36
|
+
} \
|
|
37
|
+
} while (0)
|
|
38
|
+
|
|
39
|
+
#define MU_ASSERT_EQ_SIZE(a, b) \
|
|
40
|
+
do { \
|
|
41
|
+
if ((a) != (b)) { \
|
|
42
|
+
char _mu_buf[128]; \
|
|
43
|
+
snprintf(_mu_buf, sizeof(_mu_buf), "got %zu expected %zu", (size_t)(a), (size_t)(b)); \
|
|
44
|
+
mu_fail(__FILE__, __LINE__, _mu_buf); \
|
|
45
|
+
return; \
|
|
46
|
+
} \
|
|
47
|
+
} while (0)
|
|
48
|
+
|
|
49
|
+
#define MU_ASSERT_STREQ(a, b) \
|
|
50
|
+
do { \
|
|
51
|
+
if (((a) == NULL && (b) != NULL) || ((a) != NULL && (b) == NULL) || \
|
|
52
|
+
((a) != NULL && (b) != NULL && strcmp((a), (b)) != 0)) { \
|
|
53
|
+
char _mu_buf[256]; \
|
|
54
|
+
char _mu_a[MU_SNIP_MAX]; \
|
|
55
|
+
char _mu_b[MU_SNIP_MAX]; \
|
|
56
|
+
const char* _mu_as = mu_snip((a), _mu_a, sizeof(_mu_a)); \
|
|
57
|
+
const char* _mu_bs = mu_snip((b), _mu_b, sizeof(_mu_b)); \
|
|
58
|
+
snprintf(_mu_buf, sizeof(_mu_buf), "got '%s' expected '%s'", _mu_as, _mu_bs); \
|
|
59
|
+
mu_fail(__FILE__, __LINE__, _mu_buf); \
|
|
60
|
+
return; \
|
|
61
|
+
} \
|
|
62
|
+
} while (0)
|
|
63
|
+
|
|
64
|
+
#endif
|