Affix
view release on metacpan or search on metacpan
infix/src/common/double_tap.h view on Meta::CPAN
* The documentation blocks within this file are licensed under the
* Creative Commons Attribution 4.0 International License (CC BY 4.0).
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file double_tap.h
* @brief A lightweight, single-header TAP (Test Anything Protocol) library.
* @ingroup internal_test_harness
*
* @details This file provides a simple, self-contained testing harness that produces
* TAP-compliant output, which is ideal for integration with CI/CD systems and other
* testing tools. It is used for all unit and regression tests within the `infix` project.
*
* The library is designed to be trivial to use:
* 1. Define `DBLTAP_ENABLE` and `DBLTAP_IMPLEMENTATION` in a single test file.
* 2. Write all test logic within a function named `test_body(void)` using the `TEST` macro.
* 3. Use the provided macros (`plan`, `ok`, `subtest`, etc.) to structure tests.
*
* The library provides its own `main` function that initializes the harness, calls
* the user-defined `test_body`, and reports the final results.
*
* @section thread_safety Thread Safety
*
* The design uses thread-local storage (`_Thread_local`, `__thread`) to manage the
* test state (test counts, subtest nesting, etc.). This allows multiple threads to
* run tests concurrently without interfering with each other's output or results,
* making it suitable for testing thread-safe code. Global counters use atomic
* operations where available to ensure correctness.
*
* @internal
*/
#pragma once
#ifdef DBLTAP_ENABLE
#define TAP_VERSION 13
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__unix__) || defined(__APPLE__) || defined(__OpenBSD__)
#include <unistd.h>
#endif
#if defined(_WIN32) || defined(__CYGWIN__)
#include <windows.h>
#elif (defined(__unix__) || defined(__APPLE__)) && !defined(__OpenBSD__)
// Do not include pthread.h on OpenBSD to prevent linking/cleanup issues if -pthread is not used.
#include <pthread.h>
#endif
// C++ Headers must be included BEFORE extern "C"
#if defined(__cplusplus)
#include <atomic>
#endif
#ifdef __cplusplus
extern "C" {
#endif
// Portability Macros for Atomics and Thread-Local Storage
#if defined(__cplusplus)
#define TAP_ATOMIC_SIZE_T std::atomic<size_t>
#define TAP_ATOMIC_FETCH_ADD(ptr, val) std::atomic_fetch_add(ptr, (size_t)(val))
#define TAP_ATOMIC_INIT(val) (val)
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
#include <stdatomic.h>
#define TAP_ATOMIC_SIZE_T _Atomic size_t
#define TAP_ATOMIC_FETCH_ADD(ptr, val) atomic_fetch_add(ptr, val)
#define TAP_ATOMIC_INIT(val) = val
#elif defined(__GNUC__) || defined(__clang__)
#define TAP_ATOMIC_SIZE_T size_t
#define TAP_ATOMIC_FETCH_ADD(ptr, val) __atomic_fetch_add(ptr, (size_t)(val), __ATOMIC_SEQ_CST)
#define TAP_ATOMIC_INIT(val) = val
#else
// Fallback for older compilers without atomics support. This is not thread-safe.
#define TAP_ATOMIC_SIZE_T size_t
#define TAP_ATOMIC_FETCH_ADD(ptr, val) ((*ptr) += (val))
#define TAP_ATOMIC_INIT(val) = val
#if !defined(_MSC_VER)
#warning "Compiler does not support C11 atomics or GCC builtins; global counters will not be thread-safe."
#endif
#endif
#if defined(__OpenBSD__)
// OpenBSD has known issues with TLS cleanup in some linking scenarios.
// Disable TLS to prevent segfaults at exit.
#define TAP_THREAD_LOCAL
#elif defined(__cplusplus)
#define TAP_THREAD_LOCAL thread_local
#elif defined(_MSC_VER)
// Microsoft Visual C++
#define TAP_THREAD_LOCAL __declspec(thread)
#elif defined(_WIN32) && defined(__clang__)
// Clang on Windows
#define TAP_THREAD_LOCAL __declspec(thread)
#elif defined(__GNUC__) || defined(__clang__)
// GCC (including MinGW) and Clang on *nix
#define TAP_THREAD_LOCAL __thread
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
#define TAP_THREAD_LOCAL _Thread_local
#else
#define TAP_THREAD_LOCAL
#if !defined(_MSC_VER)
#warning "Compiler does not support thread-local storage; tests will not be thread-safe."
#endif
#endif
#if defined(__GNUC__) || defined(__clang__)
#define DBLTAP_NOINLINE __attribute__((noinline))
#elif defined(_MSC_VER)
#define DBLTAP_NOINLINE __declspec(noinline)
#else
#define DBLTAP_NOINLINE
#endif
// Compiler-specific attribute for printf-style format checking.
#if defined(__GNUC__) || defined(__clang__)
#define DBLTAP_PRINTF_FORMAT(fmt_index, arg_index) __attribute__((format(printf, fmt_index, arg_index)))
#else
#define DBLTAP_PRINTF_FORMAT(fmt_index, arg_index)
#endif
// Public Test Harness Functions (wrapped by macros for convenience)
void tap_init(void);
void tap_plan(size_t count);
int tap_done(void);
void tap_bail_out(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
bool tap_ok(bool condition, const char * file, int line, const char * func, const char * expr, const char * name, ...)
DBLTAP_PRINTF_FORMAT(6, 7);
bool tap_subtest_start(const char * name);
bool tap_subtest_end(void);
void tap_todo_start(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
void tap_todo_end(void);
void tap_skip(size_t count, const char * reason, ...) DBLTAP_PRINTF_FORMAT(2, 3);
void tap_skip_all(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
void diag(const char * fmt, ...) DBLTAP_PRINTF_FORMAT(1, 2);
void tap_note(const char * fmt, ...) DBLTAP_PRINTF_FORMAT(1, 2);
void test_body(void);
#ifdef __cplusplus
}
#endif
// Public Test Harness Macros
/** @brief Declares the total number of tests to be run in the current scope. Must be called before any tests. */
#define plan(count) tap_plan(count)
/** @brief Concludes testing, validates the plan, and returns an exit code based on success or failure. */
#define done() tap_done()
/** @brief Immediately terminates the entire test suite with a failure message. Useful for fatal setup errors. */
#define bail_out(...) tap_bail_out(__VA_ARGS__)
/** @brief The core assertion macro. Checks a condition and prints an "ok" or "not ok" TAP line with diagnostics on
* failure. */
#define ok(cond, ...) tap_ok(!!(cond), __FILE__, __LINE__, __func__, #cond, __VA_ARGS__)
/** @brief A convenience macro that always passes. Equivalent to `ok(true, ...)`. */
#define pass(...) ok(true, __VA_ARGS__)
/** @brief A convenience macro that always fails. Equivalent to `ok(false, ...)`. */
#define fail(...) ok(false, __VA_ARGS__)
/** @brief Defines a block of tests as a nested subtest, which gets its own plan and pass/fail status. */
#define subtest(name) \
for (bool _tap_subtest_once = tap_subtest_start(name); _tap_subtest_once; _tap_subtest_once = tap_subtest_end())
/** @brief Marks a specified number of subsequent tests as skipped with a given reason. */
#define skip(count, ...) tap_skip(count, __VA_ARGS__)
/** @brief Marks all subsequent tests in the current scope as skipped. */
#define skip_all(...) tap_skip_all(__VA_ARGS__)
/** @brief Defines a block of tests that are expected to fail. A failure in a TODO block does not fail the overall
* suite. */
#define TODO(reason) \
for (int _tap_todo_once = (tap_todo_start(reason), 1); _tap_todo_once; _tap_todo_once = (tap_todo_end(), 0))
/** @brief Prints a diagnostic message to stderr, prefixed with '#'. Standard TAP practice for auxiliary information. */
#define diag(...) diag(__VA_ARGS__)
/** @brief Prints a diagnostic message (a note) to stdout, prefixed with '#'. */
#ifndef note
#define note(...) tap_note(__VA_ARGS__)
#endif
/** @brief Defines the main test function body where all tests are written. */
#ifdef __cplusplus
#define TEST extern "C" void test_body(void)
#else
#define TEST void test_body(void)
#endif
#else // If DBLTAP_ENABLE is not defined, provide stub macros to allow code to compile without the harness.
#define plan(count) ((void)0)
#define done() (0)
#define bail_out(...) \
do { \
fprintf(stderr, "Bail out! "); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
exit(1); \
} while (0)
#define ok(cond, ...) (true)
#define pass(...) ((void)0)
#define fail(...) ((void)0)
#define subtest(name) if (0)
#define skip(count, ...) ((void)0)
#define skip_all(...) ((void)0)
#define TODO(reason, ...) if (0)
#define diag(...) ((void)0)
#ifndef note
#define note(...) ((void)0)
#endif
#define TEST \
int main(void) { return 0; }
( run in 0.503 second using v1.01-cache-2.11-cpan-99c4e6809bf )