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