infix
A JIT-Powered FFI Library for C
Loading...
Searching...
No Matches
double_tap.h
Go to the documentation of this file.
1
43#pragma once
44
45#ifdef DBLTAP_ENABLE
46
47#define TAP_VERSION 13
48
49#include <stdarg.h>
50#include <stdbool.h>
51#include <stddef.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55
56#if defined(_WIN32) || defined(__CYGWIN__)
57#include <windows.h>
58#elif defined(__unix__) || defined(__APPLE__)
59#include <pthread.h>
60#endif
61
62// Portability Macros for Atomics and Thread-Local Storage
63
64#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
65#include <stdatomic.h>
66#define TAP_ATOMIC_SIZE_T _Atomic size_t
67#define TAP_ATOMIC_FETCH_ADD(ptr, val) atomic_fetch_add(ptr, val)
68#elif defined(__GNUC__) || defined(__clang__)
69#define TAP_ATOMIC_SIZE_T size_t
70#define TAP_ATOMIC_FETCH_ADD(ptr, val) __sync_fetch_and_add(ptr, val)
71#else
72// Fallback for older compilers without atomics support. This is not thread-safe.
73#define TAP_ATOMIC_SIZE_T size_t
74#define TAP_ATOMIC_FETCH_ADD(ptr, val) ((*ptr) += (val))
75#warning "Compiler does not support C11 atomics or GCC builtins; global counters will not be thread-safe."
76#endif
77
78#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
79#define TAP_THREAD_LOCAL _Thread_local
80#elif defined(__GNUC__) || defined(__clang__)
81#define TAP_THREAD_LOCAL __thread
82#elif defined(_MSC_VER)
83#define TAP_THREAD_LOCAL __declspec(thread)
84#else
85#define TAP_THREAD_LOCAL
86#warning "Compiler does not support thread-local storage; tests will not be thread-safe."
87#endif
88
89// Compiler-specific attribute for printf-style format checking.
90#if defined(__GNUC__) || defined(__clang__)
91#define DBLTAP_PRINTF_FORMAT(fmt_index, arg_index) __attribute__((format(printf, fmt_index, arg_index)))
92#else
93#define DBLTAP_PRINTF_FORMAT(fmt_index, arg_index)
94#endif
95
96// Public Test Harness Functions (wrapped by macros for convenience)
97
98void tap_init(void);
99void tap_plan(size_t count);
100int tap_done(void);
101void tap_bail_out(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
102bool tap_ok(bool condition, const char * file, int line, const char * func, const char * expr, const char * name, ...)
103 DBLTAP_PRINTF_FORMAT(6, 7);
104bool tap_subtest_start(const char * name);
105bool tap_subtest_end(void);
106void tap_todo_start(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
107void tap_todo_end(void);
108void tap_skip(size_t count, const char * reason, ...) DBLTAP_PRINTF_FORMAT(2, 3);
109void tap_skip_all(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
110void diag(const char * fmt, ...) DBLTAP_PRINTF_FORMAT(1, 2);
111void tap_note(const char * fmt, ...) DBLTAP_PRINTF_FORMAT(1, 2);
112
113// Public Test Harness Macros
114
116#define plan(count) tap_plan(count)
117
119#define done() tap_done()
120
122#define bail_out(...) tap_bail_out(__VA_ARGS__)
123
126#define ok(cond, ...) tap_ok(!!(cond), __FILE__, __LINE__, __func__, #cond, __VA_ARGS__)
127
129#define pass(...) ok(true, __VA_ARGS__)
130
132#define fail(...) ok(false, __VA_ARGS__)
133
135#define subtest(name) \
136 for (bool _tap_subtest_once = tap_subtest_start(name); _tap_subtest_once; _tap_subtest_once = tap_subtest_end())
137
139#define skip(count, ...) tap_skip(count, __VA_ARGS__)
140
142#define skip_all(...) tap_skip_all(__VA_ARGS__)
143
146#define TODO(reason) \
147 for (int _tap_todo_once = (tap_todo_start(reason), 1); _tap_todo_once; _tap_todo_once = (tap_todo_end(), 0))
148
150#define diag(...) diag(__VA_ARGS__)
151
153#ifndef note
154#define note(...) tap_note(__VA_ARGS__)
155#endif
156
158#define TEST void test_body(void)
159void test_body(void);
160
161#else // If DBLTAP_ENABLE is not defined, provide stub macros to allow code to compile without the harness.
162
163#define plan(count) ((void)0)
164#define done() (0)
165#define bail_out(...) \
166 do { \
167 fprintf(stderr, "Bail out! "); \
168 fprintf(stderr, __VA_ARGS__); \
169 fprintf(stderr, "\n"); \
170 exit(1); \
171 } while (0)
172#define ok(cond, ...) (true)
173#define pass(...) ((void)0)
174#define fail(...) ((void)0)
175#define subtest(name) if (0)
176#define skip(count, ...) ((void)0)
177#define skip_all(...) ((void)0)
178#define TODO(reason, ...) if (0)
179#define diag(...) ((void)0)
180#ifndef note
181#define note(...) ((void)0)
182#endif
183#define TEST \
184 int main(void) { return 0; }
185
186#endif // DBLTAP_ENABLE
187
188#if defined(DBLTAP_ENABLE) && defined(DBLTAP_IMPLEMENTATION)
189
190// Internal Test State Management
191
199typedef struct {
200 size_t plan;
201 size_t count;
202 size_t failed;
203 size_t failed_todo;
204 int indent_level;
205 bool has_plan;
206 bool skipping;
207 bool todo;
208 char subtest_name[256];
209 char todo_reason[256];
210 char skip_reason[256];
211} tap_state_t;
212
213#define MAX_DEPTH 16
214#define NO_PLAN ((size_t)-1)
217static TAP_THREAD_LOCAL tap_state_t state_stack[MAX_DEPTH];
219static TAP_THREAD_LOCAL tap_state_t * current_state = NULL;
220
222static TAP_ATOMIC_SIZE_T g_total_failed = 0;
223
224// One-Time Initialization for TAP Header
225
226#if defined(_WIN32) || defined(__CYGWIN__)
227static INIT_ONCE g_tap_init_once = INIT_ONCE_STATIC_INIT;
228static BOOL CALLBACK _tap_init_routine(PINIT_ONCE initOnce, PVOID param, PVOID * context) {
229 (void)initOnce;
230 (void)param;
231 (void)context;
232 printf("TAP version %d\n", TAP_VERSION);
233 fflush(stdout);
234 return TRUE;
235}
236#elif defined(__unix__) || defined(__APPLE__)
237static pthread_once_t g_tap_init_once = PTHREAD_ONCE_INIT;
238static void _tap_init_routine(void) {
239 printf("TAP version %d\n", TAP_VERSION);
240 fflush(stdout);
241}
242#endif
243
251static void _tap_ensure_initialized(void) {
252#if defined(_WIN32) || defined(__CYGWIN__)
253 InitOnceExecuteOnce(&g_tap_init_once, _tap_init_routine, NULL, NULL);
254#elif defined(__unix__) || defined(__APPLE__)
255 pthread_once(&g_tap_init_once, _tap_init_routine);
256#endif
257 if (!current_state) {
258 current_state = &state_stack[0];
259 memset(current_state, 0, sizeof(tap_state_t));
260 current_state->plan = NO_PLAN;
261 }
262}
263
264// Internal Helper Functions
265
267static void print_indent(FILE * stream) {
268 _tap_ensure_initialized();
269 for (int i = 0; i < current_state->indent_level; ++i)
270 fprintf(stream, " ");
271}
272
274static void push_state(void) {
275 if (current_state >= &state_stack[MAX_DEPTH - 1])
276 tap_bail_out("Exceeded maximum subtest depth of %d", MAX_DEPTH);
277 tap_state_t * parent = current_state;
278 current_state++;
279 memset(current_state, 0, sizeof(tap_state_t));
280 current_state->plan = NO_PLAN;
281 current_state->indent_level = parent->indent_level + 1;
282
283 // A subtest inherits the 'todo' state from its parent.
284 if (parent->todo) {
285 current_state->todo = true;
286 snprintf(current_state->todo_reason, sizeof(current_state->todo_reason), "%s", parent->todo_reason);
287 }
288}
289
291static void pop_state(void) {
292 if (current_state <= &state_stack[0])
293 tap_bail_out("Internal error: Attempted to pop base test state");
294 current_state--;
295}
296
297// Public API Implementation
298
299void tap_init(void) { _tap_ensure_initialized(); }
300
301void tap_plan(size_t count) {
302 _tap_ensure_initialized();
303 if (current_state->has_plan || current_state->count > 0)
304 tap_bail_out("Plan declared after tests have run or a plan was already set");
305 current_state->plan = count;
306 current_state->has_plan = true;
307 print_indent(stdout);
308 printf("1..%llu\n", (unsigned long long)count);
309 fflush(stdout);
310}
311
312bool tap_ok(bool condition, const char * file, int line, const char * func, const char * expr, const char * name, ...) {
313 _tap_ensure_initialized();
314 if (current_state->skipping) {
315 current_state->count++;
316 return true;
317 }
318
319 char name_buffer[256] = {0};
320 if (name && name[0] != '\0') {
321 va_list args;
322 va_start(args, name);
323 vsnprintf(name_buffer, sizeof(name_buffer), name, args);
324 va_end(args);
325 }
326
327 current_state->count++;
328
329 if (!condition) {
330 if (current_state->todo)
331 current_state->failed_todo++;
332 else {
333 current_state->failed++;
334 if (current_state == &state_stack[0]) // Only increment global fail count for top-level tests
335 TAP_ATOMIC_FETCH_ADD(&g_total_failed, 1);
336 }
337 }
338
339 print_indent(stdout);
340 printf("%s %llu", condition ? "ok" : "not ok", (unsigned long long)current_state->count);
341 if (name_buffer[0] != '\0')
342 printf(" - %s", name_buffer);
343
344 if (current_state->todo)
345 printf(" # TODO %s", current_state->todo_reason);
346
347 printf("\n");
348
349 if (!condition && !current_state->todo) {
350 // Print detailed diagnostics in YAML block format on failure.
351 print_indent(stdout);
352 fprintf(stdout, "#\n");
353 print_indent(stdout);
354 fprintf(stdout, "# message: 'Test failed'\n");
355 print_indent(stdout);
356 fprintf(stdout, "# severity: fail\n");
357 print_indent(stdout);
358 fprintf(stdout, "# data:\n");
359 print_indent(stdout);
360 fprintf(stdout, "# file: %s\n", file);
361 print_indent(stdout);
362 fprintf(stdout, "# line: %d\n", line);
363 print_indent(stdout);
364 fprintf(stdout, "# function: %s\n", func);
365 print_indent(stdout);
366 fprintf(stdout, "# expression: '%s'\n", expr);
367 print_indent(stdout);
368 fprintf(stdout, "# ...\n");
369 }
370 fflush(stdout);
371 return condition;
372}
373
374void tap_skip(size_t count, const char * reason, ...) {
375 _tap_ensure_initialized();
376 char buffer[256];
377 va_list args;
378 va_start(args, reason);
379 vsnprintf(buffer, sizeof(buffer), reason, args);
380 va_end(args);
381 for (size_t i = 0; i < count; ++i) {
382 current_state->count++;
383 print_indent(stdout);
384 printf("ok %llu # SKIP %s\n", (unsigned long long)current_state->count, buffer);
385 }
386 fflush(stdout);
387}
388
389void tap_skip_all(const char * reason, ...) {
390 _tap_ensure_initialized();
391 current_state->skipping = true;
392 va_list args;
393 va_start(args, reason);
394 vsnprintf(current_state->skip_reason, sizeof(current_state->skip_reason), reason, args);
395 va_end(args);
396}
397
398void tap_todo_start(const char * reason, ...) {
399 _tap_ensure_initialized();
400 current_state->todo = true;
401 va_list args;
402 va_start(args, reason);
403 vsnprintf(current_state->todo_reason, sizeof(current_state->todo_reason), reason, args);
404 va_end(args);
405}
406
407void tap_todo_end(void) {
408 _tap_ensure_initialized();
409 current_state->todo = false;
410 current_state->todo_reason[0] = '\0';
411}
412
413void diag(const char * fmt, ...) {
414 _tap_ensure_initialized();
415 char buffer[1024];
416 va_list args;
417 va_start(args, fmt);
418 vsnprintf(buffer, sizeof(buffer), fmt, args);
419 va_end(args);
420
421 print_indent(stderr);
422 fprintf(stderr, "# %s\n", buffer);
423 fflush(stderr);
424}
425
426void tap_note(const char * fmt, ...) {
427 _tap_ensure_initialized();
428 char buffer[1024];
429 va_list args;
430 va_start(args, fmt);
431 vsnprintf(buffer, sizeof(buffer), fmt, args);
432 va_end(args);
433
434 print_indent(stdout);
435 fprintf(stdout, "# %s\n", buffer);
436 fflush(stdout);
437}
438
439void tap_bail_out(const char * reason, ...) {
440 fprintf(stderr, "Bail out! ");
441 va_list args;
442 va_start(args, reason);
443 vfprintf(stderr, reason, args);
444 va_end(args);
445 fprintf(stderr, "\n");
446 fflush(stderr);
447 exit(1);
448}
449
450bool tap_subtest_start(const char * name) {
451 _tap_ensure_initialized();
452 print_indent(stdout);
453 fprintf(stdout, "# Subtest: %s\n", name);
454 fflush(stdout);
455 push_state();
456 snprintf(current_state->subtest_name, sizeof(current_state->subtest_name), "%s", name);
457 return true; // Enters the `for` loop body.
458}
459
460bool tap_subtest_end(void) {
461 _tap_ensure_initialized();
462
463 if (!current_state->has_plan) {
464 // If no plan was declared, implicitly plan for the number of tests that ran.
465 current_state->plan = current_state->count;
466 print_indent(stdout);
467 printf("1..%llu\n", (unsigned long long)current_state->plan);
468 }
469 bool plan_ok = (current_state->plan == current_state->count);
470 bool subtest_ok = (current_state->failed == 0) && plan_ok;
471
472 char name_buffer[256];
473 snprintf(name_buffer, sizeof(name_buffer), "%s", current_state->subtest_name);
474
475 pop_state(); // Return to the parent's state.
476
477 // Report the subtest's success or failure as a single test point in the parent scope.
478 ok(subtest_ok, "%s", name_buffer);
479
480 return false; // Exits the `for` loop.
481}
482
483int tap_done(void) {
484 _tap_ensure_initialized();
485
486 if (current_state != &state_stack[0])
487 tap_bail_out("tap_done() called inside a subtest");
488
489 if (!current_state->has_plan) {
490 current_state->plan = current_state->count;
491 print_indent(stdout);
492 printf("1..%llu\n", (unsigned long long)current_state->plan);
493 fflush(stdout);
494 }
495
496 if (current_state->skipping) {
497 print_indent(stdout);
498 printf("1..%llu # SKIP %s\n", (unsigned long long)current_state->plan, current_state->skip_reason);
499 fflush(stdout);
500 return 0;
501 }
502
503 if (current_state->plan != current_state->count)
504 fail("Test plan adherence (planned %llu, but ran %llu)",
505 (unsigned long long)current_state->plan,
506 (unsigned long long)current_state->count);
507
508 size_t final_failed_count = (size_t)TAP_ATOMIC_FETCH_ADD(&g_total_failed, 0);
509 if (final_failed_count > 0)
510 diag("Looks like you failed %llu out of %llu tests.",
511 (unsigned long long)final_failed_count,
512 (unsigned long long)current_state->plan);
513
514 return (int)final_failed_count;
515}
516
517// The main test runner that gets compiled into the test executable.
518int main(void) {
519 tap_init();
520 test_body();
521 return (int)tap_done();
522}
523
524#endif // DBLTAP_ENABLE && DBLTAP_IMPLEMENTATION
525
void * args[]
Definition 202_in_structs.c:64
int main(void)
Definition 821_threading_bare.c:81
#define plan(count)
Definition double_tap.h:163
#define diag(...)
Definition double_tap.h:179
#define ok(cond,...)
Definition double_tap.h:172
#define fail(...)
Definition double_tap.h:174