infix
A JIT-Powered FFI Library for C
Loading...
Searching...
No Matches
double_tap.h
Go to the documentation of this file.
1#pragma once
40// The main toggle for the entire framework.
41#ifdef DBLTAP_ENABLE
42
43#define TAP_VERSION 13
44
45#include <stdarg.h>
46#include <stdbool.h>
47#include <stddef.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51
52// Platform-specific includes for one-time global initialization.
53#if defined(_WIN32) || defined(__CYGWIN__)
54#include <windows.h>
55#elif defined(__unix__) || defined(__APPLE__)
56#include <pthread.h>
57#endif
58
59// C11 atomics with fallbacks for lock-free global counters.
60#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
61#include <stdatomic.h>
62#define TAP_ATOMIC_SIZE_T _Atomic size_t
63#define TAP_ATOMIC_FETCH_ADD(ptr, val) atomic_fetch_add(ptr, val)
64#elif defined(__GNUC__) || defined(__clang__)
65#define TAP_ATOMIC_SIZE_T size_t
66#define TAP_ATOMIC_FETCH_ADD(ptr, val) __sync_fetch_and_add(ptr, val)
67#else
68// Fallback for compilers without atomics: not thread-safe.
69#define TAP_ATOMIC_SIZE_T size_t
70#define TAP_ATOMIC_FETCH_ADD(ptr, val) ((*ptr) += (val))
71#warning "Compiler does not support C11 atomics or GCC builtins; global counters will not be thread-safe."
72#endif
73
74// C11 thread-local storage with fallbacks for older compilers.
75#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
76#define TAP_THREAD_LOCAL _Thread_local
77#elif defined(__GNUC__) || defined(__clang__)
78#define TAP_THREAD_LOCAL __thread
79#elif defined(_MSC_VER)
80#define TAP_THREAD_LOCAL __declspec(thread)
81#else
82#define TAP_THREAD_LOCAL
83#warning "Compiler does not support thread-local storage; tests will not be thread-safe."
84#endif
85
86// For printf-like format checking by GCC/Clang.
87#if defined(__GNUC__) || defined(__clang__)
88#define DBLTAP_PRINTF_FORMAT(fmt_index, arg_index) __attribute__((format(printf, fmt_index, arg_index)))
89#else
90#define DBLTAP_PRINTF_FORMAT(fmt_index, arg_index)
91#endif
92
93void tap_init(void);
94void tap_plan(size_t count);
95int tap_done(void);
96void tap_bail_out(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
97bool tap_ok(bool condition, const char * file, int line, const char * func, const char * expr, const char * name, ...)
98 DBLTAP_PRINTF_FORMAT(6, 7);
99bool tap_subtest_start(const char * name);
100bool tap_subtest_end(void);
101void tap_todo_start(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
102void tap_todo_end(void);
103void tap_skip(size_t count, const char * reason, ...) DBLTAP_PRINTF_FORMAT(2, 3);
104void tap_skip_all(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
105void diag(const char * fmt, ...) DBLTAP_PRINTF_FORMAT(1, 2);
106void tap_note(const char * fmt, ...) DBLTAP_PRINTF_FORMAT(1, 2);
107
108#define plan(count) tap_plan(count)
109#define done() tap_done()
110#define bail_out(...) tap_bail_out(__VA_ARGS__)
111#define ok(cond, ...) tap_ok(!!(cond), __FILE__, __LINE__, __func__, #cond, __VA_ARGS__)
112#define pass(...) ok(true, __VA_ARGS__)
113#define fail(...) ok(false, __VA_ARGS__)
114#define subtest(name) \
115 for (bool _tap_subtest_once = tap_subtest_start(name); _tap_subtest_once; _tap_subtest_once = tap_subtest_end())
116#define skip(count, ...) tap_skip(count, __VA_ARGS__)
117#define skip_all(...) tap_skip_all(__VA_ARGS__)
118#define TODO(reason) \
119 for (int _tap_todo_once = (tap_todo_start(reason), 1); _tap_todo_once; _tap_todo_once = (tap_todo_end(), 0))
120#define diag(...) diag(__VA_ARGS__)
121
122#ifndef note
123#define note(...) tap_note(__VA_ARGS__)
124#endif
125
126#define TEST void test_body(void)
127void test_body(void);
128
129#else // DBLTAP_ENABLE is NOT defined
130
131// No-Op Stubs for when testing is disabled.
132#define plan(count) ((void)0)
133#define done() (0)
134#define bail_out(...) \
135 do { \
136 fprintf(stderr, "Bail out! "); \
137 fprintf(stderr, __VA_ARGS__); \
138 fprintf(stderr, "\n"); \
139 exit(1); \
140 } while (0)
141#define ok(cond, ...) (true)
142#define pass(...) ((void)0)
143#define fail(...) ((void)0)
144#define subtest(name) if (0)
145#define skip(count, ...) ((void)0)
146#define skip_all(...) ((void)0)
147#define TODO(reason, ...) if (0)
148#define diag(...) ((void)0)
149#ifndef note
150#define note(...) ((void)0)
151#endif
152#define TEST \
153 int main(void) { \
154 return 0; \
155 }
156
157#endif // DBLTAP_ENABLE
158
159#if defined(DBLTAP_ENABLE) && defined(DBLTAP_IMPLEMENTATION)
160
161/*
162 * @internal
163 * @brief Holds the complete state for a single test context (main test or subtest).
164 * Each thread gets its own stack of these structures to manage nested subtests.
165 */
166typedef struct {
167 size_t plan;
168 size_t count;
169 size_t failed;
170 size_t failed_todo;
171 int indent_level;
172 bool has_plan;
173 bool skipping;
174 bool todo;
175 char subtest_name[256];
176 char todo_reason[256];
177 char skip_reason[256];
178} tap_state_t;
179
180#define MAX_DEPTH 16
181#define NO_PLAN ((size_t)-1)
182
183// Thread-Safe State Management
184
185/*
186 * Each thread gets its own private state stack using Thread-Local Storage (TLS).
187 * This is the key to achieving thread-safety without locks for most operations.
188 * `current_state` points to the active state on the current thread's stack.
189 */
190static TAP_THREAD_LOCAL tap_state_t state_stack[MAX_DEPTH];
191static TAP_THREAD_LOCAL tap_state_t * current_state = NULL;
192
193/*
194 * A global counter for the final exit code, updated using lock-free atomics.
195 * This is the only piece of state shared between threads.
196 */
197static TAP_ATOMIC_SIZE_T g_total_failed = 0;
198
199/*
200 * One-time initialization for global setup (e.g., printing the TAP version header).
201 * This uses platform-specific, thread-safe "once" mechanisms.
202 */
203#if defined(_WIN32) || defined(__CYGWIN__)
204static INIT_ONCE g_tap_init_once = INIT_ONCE_STATIC_INIT;
205static BOOL CALLBACK _tap_init_routine(PINIT_ONCE initOnce, PVOID param, PVOID * context) {
206 (void)initOnce;
207 (void)param;
208 (void)context;
209 printf("TAP version %d\n", TAP_VERSION);
210 fflush(stdout);
211 return TRUE;
212}
213#elif defined(__unix__) || defined(__APPLE__)
214static pthread_once_t g_tap_init_once = PTHREAD_ONCE_INIT;
215static void _tap_init_routine(void) {
216 printf("TAP version %d\n", TAP_VERSION);
217 fflush(stdout);
218}
219#endif
220
221/*
222 * This internal function must be called at the start of every public API function.
223 * It ensures both global (TAP version) and thread-local (current_state) initialization
224 * have occurred for the current thread.
225 */
226static void _tap_ensure_initialized(void) {
227#if defined(_WIN32) || defined(__CYGWIN__)
228 InitOnceExecuteOnce(&g_tap_init_once, _tap_init_routine, NULL, NULL);
229#elif defined(__unix__) || defined(__APPLE__)
230 pthread_once(&g_tap_init_once, _tap_init_routine);
231#endif
232 if (!current_state) {
233 current_state = &state_stack[0];
234 memset(current_state, 0, sizeof(tap_state_t));
235 current_state->plan = NO_PLAN;
236 }
237}
238
239// Private Helper Functions
240
241static void print_indent(FILE * stream) {
242 _tap_ensure_initialized();
243 for (int i = 0; i < current_state->indent_level; ++i)
244 fprintf(stream, " ");
245}
246
247/*
248 * Pushes a new, clean state onto the current thread's state stack.
249 * Used when entering a subtest.
250 */
251static void push_state(void) {
252 if (current_state >= &state_stack[MAX_DEPTH - 1])
253 tap_bail_out("Exceeded maximum subtest depth of %d", MAX_DEPTH);
254 tap_state_t * parent = current_state;
255 current_state++;
256 memset(current_state, 0, sizeof(tap_state_t));
257 current_state->plan = NO_PLAN;
258 current_state->indent_level = parent->indent_level + 1;
259 // Inherit the 'todo' status from the parent.
260 if (parent->todo) {
261 current_state->todo = true;
262 snprintf(current_state->todo_reason, sizeof(current_state->todo_reason), "%s", parent->todo_reason);
263 }
264}
265
266/*
267 * Pops the current state, returning to the parent's context.
268 * Used when exiting a subtest.
269 */
270static void pop_state(void) {
271 if (current_state <= &state_stack[0])
272 tap_bail_out("Internal error: Attempted to pop base test state");
273 current_state--;
274}
275
276// Public API Implementation
277
278void tap_init(void) {
279 _tap_ensure_initialized();
280}
281
282void tap_plan(size_t count) {
283 _tap_ensure_initialized();
284 if (current_state->has_plan || current_state->count > 0)
285 tap_bail_out("Plan declared after tests have run or a plan was already set");
286 current_state->plan = count;
287 current_state->has_plan = true;
288 print_indent(stdout);
289 printf("1..%llu\n", (unsigned long long)count);
290 fflush(stdout);
291}
292
293bool tap_ok(bool condition, const char * file, int line, const char * func, const char * expr, const char * name, ...) {
294 _tap_ensure_initialized();
295 if (current_state->skipping) {
296 current_state->count++; // Skipped tests still count towards the plan.
297 return true;
298 }
299
300 char name_buffer[256] = {0};
301 if (name && name[0] != '\0') {
302 va_list args;
303 va_start(args, name);
304 vsnprintf(name_buffer, sizeof(name_buffer), name, args);
305 va_end(args);
306 }
307
308 current_state->count++;
309
310 if (!condition) {
311 if (current_state->todo)
312 current_state->failed_todo++;
313 else {
314 current_state->failed++;
315 if (current_state == &state_stack[0])
316 TAP_ATOMIC_FETCH_ADD(&g_total_failed, 1);
317 }
318 }
319
320 print_indent(stdout);
321 printf("%s %llu", condition ? "ok" : "not ok", (unsigned long long)current_state->count);
322 if (name_buffer[0] != '\0')
323 printf(" - %s", name_buffer);
324
325 if (current_state->todo)
326 printf(" # TODO %s", current_state->todo_reason);
327
328 printf("\n");
329
330 if (!condition && !current_state->todo) {
331 // In case of failure, print diagnostic information in YAML block format.
332 print_indent(stdout);
333 fprintf(stdout, "#\n");
334 print_indent(stdout);
335 fprintf(stdout, "# message: 'Test failed'\n");
336 print_indent(stdout);
337 fprintf(stdout, "# severity: fail\n");
338 print_indent(stdout);
339 fprintf(stdout, "# data:\n");
340 print_indent(stdout);
341 fprintf(stdout, "# file: %s\n", file);
342 print_indent(stdout);
343 fprintf(stdout, "# line: %d\n", line);
344 print_indent(stdout);
345 fprintf(stdout, "# function: %s\n", func);
346 print_indent(stdout);
347 fprintf(stdout, "# expression: '%s'\n", expr);
348 print_indent(stdout);
349 fprintf(stdout, "# ...\n");
350 }
351 fflush(stdout);
352 return condition;
353}
354
355void tap_skip(size_t count, const char * reason, ...) {
356 _tap_ensure_initialized();
357 char buffer[256];
358 va_list args;
359 va_start(args, reason);
360 vsnprintf(buffer, sizeof(buffer), reason, args);
361 va_end(args);
362 for (size_t i = 0; i < count; ++i) {
363 current_state->count++;
364 print_indent(stdout);
365 printf("ok %llu # SKIP %s\n", (unsigned long long)current_state->count, buffer);
366 }
367 fflush(stdout);
368}
369
370void tap_skip_all(const char * reason, ...) {
371 _tap_ensure_initialized();
372 current_state->skipping = true;
373 va_list args;
374 va_start(args, reason);
375 vsnprintf(current_state->skip_reason, sizeof(current_state->skip_reason), reason, args);
376 va_end(args);
377}
378
379void tap_todo_start(const char * reason, ...) {
380 _tap_ensure_initialized();
381 current_state->todo = true;
382 va_list args;
383 va_start(args, reason);
384 vsnprintf(current_state->todo_reason, sizeof(current_state->todo_reason), reason, args);
385 va_end(args);
386}
387
388void tap_todo_end(void) {
389 _tap_ensure_initialized();
390 current_state->todo = false;
391 current_state->todo_reason[0] = '\0';
392}
393
394void diag(const char * fmt, ...) {
395 _tap_ensure_initialized();
396 print_indent(stderr);
397 fprintf(stderr, "# ");
398 va_list args;
399 va_start(args, fmt);
400 vfprintf(stderr, fmt, args);
401 va_end(args);
402 fputs("\n", stderr);
403 fflush(stderr);
404}
405
406void tap_note(const char * fmt, ...) {
407 _tap_ensure_initialized();
408 print_indent(stdout);
409 fprintf(stdout, "# ");
410 va_list args;
411 va_start(args, fmt);
412 vfprintf(stdout, fmt, args);
413 va_end(args);
414 fputs("\n", stdout);
415 fflush(stdout);
416}
417
418void tap_bail_out(const char * reason, ...) {
419 fprintf(stderr, "Bail out! ");
420 va_list args;
421 va_start(args, reason);
422 vfprintf(stderr, reason, args);
423 va_end(args);
424 fprintf(stderr, "\n");
425 fflush(stderr);
426 exit(1);
427}
428
429bool tap_subtest_start(const char * name) {
430 _tap_ensure_initialized();
431 print_indent(stdout);
432 fprintf(stdout, "# Subtest: %s\n", name);
433 fflush(stdout);
434 push_state();
435 snprintf(current_state->subtest_name, sizeof(current_state->subtest_name), "%s", name);
436 return true; // Allows use in a for-loop macro.
437}
438
439bool tap_subtest_end(void) {
440 _tap_ensure_initialized();
441
442 // If the subtest didn't have an explicit plan, create one from the count.
443 if (!current_state->has_plan) {
444 current_state->plan = current_state->count;
445 print_indent(stdout);
446 printf("1..%llu\n", (unsigned long long)current_state->plan);
447 }
448 bool plan_ok = (current_state->plan == current_state->count);
449 bool subtest_ok = (current_state->failed == 0) && plan_ok;
450
451 char name_buffer[256];
452 snprintf(name_buffer, sizeof(name_buffer), "%s", current_state->subtest_name);
453
454 // Return to parent's context before reporting the subtest's result.
455 pop_state();
456
457 // Report the success/failure of the entire subtest as a single test point.
458 ok(subtest_ok, "%s", name_buffer);
459
460 return false; // Ensures the `subtest()` for-loop only runs once.
461}
462
463int tap_done(void) {
464 _tap_ensure_initialized();
465 // tap_done() should only be called from the main test context.
466 if (current_state != &state_stack[0])
467 tap_bail_out("tap_done() called inside a subtest");
468
469 // If no plan was ever declared, create one from the total count.
470 if (!current_state->has_plan) {
471 current_state->plan = current_state->count;
472 print_indent(stdout);
473 printf("1..%llu\n", (unsigned long long)current_state->plan);
474 fflush(stdout);
475 }
476
477 if (current_state->skipping) {
478 print_indent(stdout);
479 printf("1..%llu # SKIP %s\n", (unsigned long long)current_state->plan, current_state->skip_reason);
480 fflush(stdout);
481 return 0;
482 }
483
484 if (current_state->plan != current_state->count)
485 fail("Test plan adherence (planned %llu, but ran %llu)",
486 (unsigned long long)current_state->plan,
487 (unsigned long long)current_state->count);
488
489 size_t final_failed_count = (size_t)TAP_ATOMIC_FETCH_ADD(&g_total_failed, 0);
490 if (final_failed_count > 0)
491 diag("Looks like you failed %llu out of %llu tests.",
492 (unsigned long long)final_failed_count,
493 (unsigned long long)current_state->plan);
494
495 return (int)final_failed_count;
496}
497
498/*
499 * The entry point of the test program when DBLTAP_ENABLE is defined.
500 */
501int main(void) {
502 tap_init();
503 test_body();
504 return (int)tap_done();
505}
506
507#endif // defined(DBLTAP_ENABLE) && defined(DBLTAP_IMPLEMENTATION)
void * args[]
Definition 202_in_structs.c:75
int main(void)
Definition 821_threading_bare.c:101
#define plan(count)
Definition double_tap.h:132
#define diag(...)
Definition double_tap.h:148
#define ok(cond,...)
Definition double_tap.h:141
#define fail(...)
Definition double_tap.h:143