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