Home | History | Annotate | Line # | Download | only in tests
      1 // SPDX-License-Identifier: 0BSD
      2 
      3 ///////////////////////////////////////////////////////////////////////////////
      4 //
      5 /// \file       tuktest.h
      6 /// \brief      Helper macros for writing simple test programs
      7 /// \version    2024-02-14
      8 ///
      9 /// Some inspiration was taken from Seatest by Keith Nicholas and
     10 /// from STest which is a fork of Seatest by Jia Tan.
     11 ///
     12 /// This is standard C99/C11 only and thus should be fairly portable
     13 /// outside POSIX systems too.
     14 ///
     15 /// This supports putting multiple tests in a single test program
     16 /// although it is perfectly fine to have only one test per program.
     17 /// Each test can produce one of these results:
     18 ///   - Pass
     19 ///   - Fail
     20 ///   - Skip
     21 ///   - Hard error (the remaining tests, if any, are not run)
     22 ///
     23 /// By default this produces an exit status that is compatible with
     24 /// Automake and Meson, and mostly compatible with CMake.[1]
     25 /// If a test program contains multiple tests, only one exit code can
     26 /// be returned. Of the following, the first match is used:
     27 ///   - 99 if any test returned a hard error
     28 ///   - stdlib.h's EXIT_FAILURE if at least one test failed
     29 ///   - 77 if at least one test was skipped or no tests were run at all
     30 ///   - stdlib.h's EXIT_SUCCESS (0 on POSIX); that is, if none of the above
     31 ///     are true then there was at least one test to run and none of them
     32 ///     failed, was skipped, or returned a hard error.
     33 ///
     34 /// A summary of tests being run and their results are printed to stdout.
     35 /// If you want ANSI coloring for the output, #define TUKTEST_COLOR.
     36 /// If you only want output when something goes wrong, #define TUKTEST_QUIET.
     37 ///
     38 /// The downside of the above mapping is that it cannot indicate if
     39 /// some tests were skipped and some passed. If that is likely to
     40 /// happen it may be better to split into multiple test programs (one
     41 /// test per program) or use the TAP mode described below.
     42 ///
     43 /// By using #define TUKTEST_TAP before #including this file the
     44 /// output will be Test Anything Protocol (TAP) version 12 compatible
     45 /// and the exit status will always be EXIT_SUCCESS. This can be easily
     46 /// used with Automake via its tap-driver.sh. Meson supports TAP natively.
     47 /// TAP's todo-directive isn't supported for now, mostly because it's not
     48 /// trivially convertible to the exit-status reporting method.
     49 ///
     50 /// If TUKTEST_TAP is used, TUKTEST_QUIET and TUKTEST_COLOR are ignored.
     51 ///
     52 /// The main() function may look like this (remember to include config.h
     53 /// or such files too if needed!):
     54 ///
     55 ///     #include "tuktest.h"
     56 ///
     57 ///     int main(int argc, char **argv)
     58 ///     {
     59 ///         tuktest_start(argc, argv);
     60 ///
     61 ///         if (!is_package_foo_available())
     62 ///             tuktest_early_skip("Optional package foo is not available");
     63 ///
     64 ///         if (!do_common_initializations())
     65 ///             tuktest_error("Error during common initializations");
     66 ///
     67 ///         tuktest_run(testfunc1);
     68 ///         tuktest_run(testfunc2);
     69 ///
     70 ///         return tuktest_end();
     71 ///     }
     72 ///
     73 /// Using exit(tuktest_end()) as a pair to tuktest_start() is OK too.
     74 ///
     75 /// Each test function called via tuktest_run() should be of type
     76 /// "void testfunc1(void)". The test functions should use the
     77 /// various assert_CONDITION() macros. The current test stops if
     78 /// an assertion fails (this is implemented with setjmp/longjmp).
     79 /// Execution continues from the next test unless the failure was
     80 /// due to assert_error() (indicating a hard error) which makes
     81 /// the program exit() without running any remaining tests.
     82 ///
     83 /// Search for "define assert" in this file to find the explanations
     84 /// of the available assertion macros.
     85 ///
     86 /// IMPORTANT:
     87 ///
     88 ///   - The assert_CONDITION() macros may only be used by code that is
     89 ///     called via tuktest_run()! This includes the function named in
     90 ///     the tuktest_run() call and functions called further from there.
     91 ///     (The assert_CONDITION() macros depend on setup code in tuktest_run()
     92 ///     and other use results in undefined behavior.)
     93 ///
     94 ///   - tuktest_start(), tuktest_early_skip, tuktest_run(), and tuktest_end()
     95 ///     must not be used in the tests called via tuktest_run()! (tuktest_end()
     96 ///     is called more freely internally by this file but such use isn't part
     97 ///     of the API.)
     98 ///
     99 ///   - tuktest_error(), tuktest_malloc(), tuktest_free(),
    100 ///     tuktest_file_from_srcdir(), and tuktest_file_from_builddir()
    101 ///     can be used everywhere after tuktest_start() has been called.
    102 ///     (In tests running under tuktest_run(), assert_error() can be used
    103 ///     instead of tuktest_error() when a hard error occurs.)
    104 ///
    105 ///   - Everything else is for internal use only.
    106 ///
    107 /// Footnotes:
    108 ///
    109 /// [1] As of 2022-06-02:
    110 ///     See the Automake manual "info (automake)Scripts-based Testsuites" or:
    111 ///     https://www.gnu.org/software/automake/manual/automake.html#Scripts_002dbased-Testsuites
    112 ///
    113 ///     Meson: https://mesonbuild.com/Unit-tests.html
    114 ///
    115 ///     CMake handles passing and failing tests by default but treats hard
    116 ///     errors as regular fails. To make CMake support skipped tests
    117 ///     correctly, one has to set the SKIP_RETURN_CODE property for each test:
    118 ///
    119 ///     set_tests_properties(foo_test_name PROPERTIES SKIP_RETURN_CODE 77)
    120 ///
    121 ///     See:
    122 ///     https://cmake.org/cmake/help/latest/command/set_tests_properties.html
    123 ///     https://cmake.org/cmake/help/latest/prop_test/SKIP_RETURN_CODE.html
    124 //
    125 //  Author:    Lasse Collin
    126 //
    127 ///////////////////////////////////////////////////////////////////////////////
    128 
    129 #ifndef TUKTEST_H
    130 #define TUKTEST_H
    131 
    132 #include <stddef.h>
    133 
    134 // On some (too) old systems inttypes.h doesn't exist or isn't good enough.
    135 // Include it conditionally so that any portability tricks can be done before
    136 // tuktest.h is included. On any modern system inttypes.h is fine as is.
    137 #ifndef PRIu64
    138 #	include <inttypes.h>
    139 #endif
    140 
    141 #include <setjmp.h>
    142 #include <stdlib.h>
    143 #include <string.h>
    144 #include <stdio.h>
    145 
    146 
    147 #if defined(__GNUC__) && defined(__GNUC_MINOR__)
    148 #	define TUKTEST_GNUC_REQ(major, minor) \
    149 		((__GNUC__ == (major) && __GNUC_MINOR__ >= (minor)) \
    150 			|| __GNUC__ > (major))
    151 #else
    152 #	define TUKTEST_GNUC_REQ(major, minor) 0
    153 #endif
    154 
    155 
    156 // This is silencing warnings about unused functions. Not all test programs
    157 // need all functions from this header.
    158 #if TUKTEST_GNUC_REQ(3, 0) || defined(__clang__)
    159 #	define tuktest_maybe_unused __attribute__((__unused__))
    160 #else
    161 #	define tuktest_maybe_unused
    162 #endif
    163 
    164 // We need printf("") so silence the warning about empty format string.
    165 #if TUKTEST_GNUC_REQ(4, 2) || defined(__clang__)
    166 #	pragma GCC diagnostic ignored "-Wformat-zero-length"
    167 #endif
    168 
    169 
    170 // Types and printf format macros to use in integer assertions and also for
    171 // printing size_t values (C99's %zu isn't available on very old systems).
    172 typedef int64_t tuktest_int;
    173 typedef uint64_t tuktest_uint;
    174 #define TUKTEST_PRId PRId64
    175 #define TUKTEST_PRIu PRIu64
    176 #define TUKTEST_PRIX PRIX64
    177 
    178 
    179 // When TAP mode isn't used, Automake-compatible exit statuses are used.
    180 #define TUKTEST_EXIT_PASS EXIT_SUCCESS
    181 #define TUKTEST_EXIT_FAIL EXIT_FAILURE
    182 #define TUKTEST_EXIT_SKIP 77
    183 #define TUKTEST_EXIT_ERROR 99
    184 
    185 
    186 enum tuktest_result {
    187 	TUKTEST_PASS,
    188 	TUKTEST_FAIL,
    189 	TUKTEST_SKIP,
    190 	TUKTEST_ERROR,
    191 };
    192 
    193 
    194 #ifdef TUKTEST_TAP
    195 #	undef TUKTEST_QUIET
    196 #	undef TUKTEST_COLOR
    197 #	undef TUKTEST_TAP
    198 #	define TUKTEST_TAP 1
    199 #	define TUKTEST_STR_PASS  "ok -"
    200 #	define TUKTEST_STR_FAIL  "not ok -"
    201 #	define TUKTEST_STR_SKIP  "ok - # SKIP"
    202 #	define TUKTEST_STR_ERROR "Bail out!"
    203 #else
    204 #	define TUKTEST_TAP 0
    205 #	ifdef TUKTEST_COLOR
    206 #		define TUKTEST_COLOR_PASS  "\x1B[0;32m"
    207 #		define TUKTEST_COLOR_FAIL  "\x1B[0;31m"
    208 #		define TUKTEST_COLOR_SKIP  "\x1B[1;34m"
    209 #		define TUKTEST_COLOR_ERROR "\x1B[0;35m"
    210 #		define TUKTEST_COLOR_TOTAL "\x1B[1m"
    211 #		define TUKTEST_COLOR_OFF   "\x1B[m"
    212 #		define TUKTEST_COLOR_IF(cond, color) ((cond) ? (color) : "" )
    213 #	else
    214 #		define TUKTEST_COLOR_PASS  ""
    215 #		define TUKTEST_COLOR_FAIL  ""
    216 #		define TUKTEST_COLOR_SKIP  ""
    217 #		define TUKTEST_COLOR_ERROR ""
    218 #		define TUKTEST_COLOR_TOTAL ""
    219 #		define TUKTEST_COLOR_OFF   ""
    220 #		define TUKTEST_COLOR_IF(cond, color) ""
    221 #	endif
    222 #	define TUKTEST_COLOR_ADD(str, color) color str TUKTEST_COLOR_OFF
    223 #	define TUKTEST_STR_PASS \
    224 		TUKTEST_COLOR_ADD("PASS:", TUKTEST_COLOR_PASS)
    225 #	define TUKTEST_STR_FAIL \
    226 		TUKTEST_COLOR_ADD("FAIL:", TUKTEST_COLOR_FAIL)
    227 #	define TUKTEST_STR_SKIP \
    228 		TUKTEST_COLOR_ADD("SKIP:", TUKTEST_COLOR_SKIP)
    229 #	define TUKTEST_STR_ERROR \
    230 		TUKTEST_COLOR_ADD("ERROR:", TUKTEST_COLOR_ERROR)
    231 #endif
    232 
    233 // NOTE: If TUKTEST_TAP is defined then TUKTEST_QUIET will get undefined above.
    234 #ifndef TUKTEST_QUIET
    235 #	define TUKTEST_QUIET 0
    236 #else
    237 #	undef TUKTEST_QUIET
    238 #	define TUKTEST_QUIET 1
    239 #endif
    240 
    241 
    242 // Counts of the passed, failed, skipped, and hard-errored tests.
    243 // This is indexed with the enumeration constants from enum tuktest_result.
    244 static unsigned tuktest_stats[4] = { 0, 0, 0, 0 };
    245 
    246 // Copy of argc and argv from main(). These are set by tuktest_start().
    247 static int tuktest_argc = 0;
    248 static char **tuktest_argv = NULL;
    249 
    250 // Name of the currently-running test. This exists because it's nice
    251 // to print the main test function name even if the failing test-assertion
    252 // fails in a function called by the main test function.
    253 static const char *tuktest_name = NULL;
    254 
    255 // longjmp() target for when a test-assertion fails.
    256 static jmp_buf tuktest_jmpenv;
    257 
    258 
    259 // This declaration is needed for tuktest_malloc().
    260 static int tuktest_end(void);
    261 
    262 
    263 // Internal helper for handling hard errors both inside and
    264 // outside tuktest_run().
    265 #define tuktest_error_impl(filename, line, ...) \
    266 do { \
    267 	tuktest_print_result_prefix(TUKTEST_ERROR, filename, line); \
    268 	printf(__VA_ARGS__); \
    269 	printf("\n"); \
    270 	++tuktest_stats[TUKTEST_ERROR]; \
    271 	exit(tuktest_end()); \
    272 } while (0)
    273 
    274 
    275 // printf() is without checking its return value in many places. This function
    276 // is called before exiting to check the status of stdout and catch errors.
    277 static void
    278 tuktest_catch_stdout_errors(void)
    279 {
    280 	if (ferror(stdout) || fclose(stdout)) {
    281 		fputs("Error while writing to stdout\n", stderr);
    282 		exit(TUKTEST_EXIT_ERROR);
    283 	}
    284 }
    285 
    286 
    287 // A simplified basename()-like function that is good enough for
    288 // cleaning up __FILE__. This supports / and \ as path separator.
    289 // If the path separator is wrong then the full path will be printed;
    290 // it's a cosmetic problem only.
    291 static const char *
    292 tuktest_basename(const char *filename)
    293 {
    294 	for (const char *p = filename + strlen(filename); p > filename; --p)
    295 		if (*p == '/' || *p == '\\')
    296 			return p + 1;
    297 
    298 	return filename;
    299 }
    300 
    301 
    302 // Internal helper that prints the prefix of the fail/skip/error message line.
    303 static void
    304 tuktest_print_result_prefix(enum tuktest_result result,
    305 		const char *filename, unsigned line)
    306 {
    307 	// This is never called with TUKTEST_PASS but I kept it here anyway.
    308 	const char *result_str
    309 			= result == TUKTEST_PASS ? TUKTEST_STR_PASS
    310 			: result == TUKTEST_FAIL ? TUKTEST_STR_FAIL
    311 			: result == TUKTEST_SKIP ? TUKTEST_STR_SKIP
    312 			: TUKTEST_STR_ERROR;
    313 
    314 	const char *short_filename = tuktest_basename(filename);
    315 
    316 	if (tuktest_name != NULL)
    317 		printf("%s %s [%s:%u] ", result_str, tuktest_name,
    318 				short_filename, line);
    319 	else
    320 		printf("%s [%s:%u] ", result_str, short_filename, line);
    321 }
    322 
    323 
    324 // An entry for linked list of memory allocations.
    325 struct tuktest_malloc_record {
    326 	struct tuktest_malloc_record *next;
    327 	void *p;
    328 };
    329 
    330 // Linked list of per-test allocations. This is used when under tuktest_run().
    331 // These allocations are freed in tuktest_run() and, in case of a hard error,
    332 // also in tuktest_end().
    333 static struct tuktest_malloc_record *tuktest_malloc_test = NULL;
    334 
    335 // Linked list of global allocations. This is used allocations are made
    336 // outside tuktest_run(). These are freed in tuktest_end().
    337 static struct tuktest_malloc_record *tuktest_malloc_global = NULL;
    338 
    339 
    340 /// A wrapper for malloc() that never return NULL and the allocated memory is
    341 /// automatically freed at the end of tuktest_run() (if allocation was done
    342 /// within a test) or early in tuktest_end() (if allocation was done outside
    343 /// tuktest_run()).
    344 ///
    345 /// If allocation fails, a hard error is reported and this function won't
    346 /// return. Possible other tests won't be run (this will call exit()).
    347 #define tuktest_malloc(size) tuktest_malloc_impl(size, __FILE__, __LINE__)
    348 
    349 static void *
    350 tuktest_malloc_impl(size_t size, const char *filename, unsigned line)
    351 {
    352 	void *p = malloc(size == 0 ? 1 : size);
    353 	struct tuktest_malloc_record *r = malloc(sizeof(*r));
    354 
    355 	if (p == NULL || r == NULL) {
    356 		free(r);
    357 		free(p);
    358 
    359 		// Avoid %zu for portability to very old systems that still
    360 		// can compile C99 code.
    361 		tuktest_error_impl(filename, line,
    362 				"tuktest_malloc(%" TUKTEST_PRIu ") failed",
    363 				(tuktest_uint)size);
    364 	}
    365 
    366 	r->p = p;
    367 
    368 	if (tuktest_name == NULL) {
    369 		// We were called outside tuktest_run().
    370 		r->next = tuktest_malloc_global;
    371 		tuktest_malloc_global = r;
    372 	} else {
    373 		// We were called under tuktest_run().
    374 		r->next = tuktest_malloc_test;
    375 		tuktest_malloc_test = r;
    376 	}
    377 
    378 	return p;
    379 }
    380 
    381 
    382 /// Frees memory allocated using tuktest_malloc(). Usually this isn't needed
    383 /// as the memory is freed automatically.
    384 ///
    385 /// NULL is silently ignored.
    386 ///
    387 /// NOTE: Under tuktest_run() only memory allocated there can be freed.
    388 /// That is,  allocations done outside tuktest_run() can only be freed
    389 /// outside tuktest_run().
    390 #define tuktest_free(ptr) tuktest_free_impl(ptr, __FILE__, __LINE__)
    391 
    392 static void
    393 tuktest_free_impl(void *p, const char *filename, unsigned line)
    394 {
    395 	if (p == NULL)
    396 		return;
    397 
    398 	struct tuktest_malloc_record **r = tuktest_name != NULL
    399 			? &tuktest_malloc_test : &tuktest_malloc_global;
    400 
    401 	while (*r != NULL) {
    402 		struct tuktest_malloc_record *tmp = *r;
    403 
    404 		if (tmp->p == p) {
    405 			*r = tmp->next;
    406 			free(p);
    407 			free(tmp);
    408 			return;
    409 		}
    410 
    411 		r = &tmp->next;
    412 	}
    413 
    414 	tuktest_error_impl(filename, line, "tuktest_free: "
    415 			"Allocation matching the pointer was not found");
    416 }
    417 
    418 
    419 // Frees all allocates in the given record list. The argument must be
    420 // either &tuktest_malloc_test or &tuktest_malloc_global.
    421 static void
    422 tuktest_free_all(struct tuktest_malloc_record **r)
    423 {
    424 	while (*r != NULL) {
    425 		struct tuktest_malloc_record *tmp = *r;
    426 		*r = tmp->next;
    427 		free(tmp->p);
    428 		free(tmp);
    429 	}
    430 }
    431 
    432 
    433 /// Initialize the test framework. No other functions or macros
    434 /// from this file may be called before calling this.
    435 ///
    436 /// If the arguments from main() aren't available, use 0 and NULL.
    437 /// If these are set, then only a subset of tests can be run by
    438 /// specifying their names on the command line.
    439 #define tuktest_start(argc, argv) \
    440 do { \
    441 	tuktest_argc = argc; \
    442 	tuktest_argv = argv; \
    443 	if (!TUKTEST_TAP && !TUKTEST_QUIET) \
    444 		printf("=== %s ===\n", tuktest_basename(__FILE__)); \
    445 } while (0)
    446 
    447 
    448 /// If it can be detected early that no tests can be run, this macro can
    449 /// be called after tuktest_start() but before any tuktest_run() to print
    450 /// a reason why the tests were skipped. Note that this macro calls exit().
    451 ///
    452 /// Using "return tuktest_end();" in main() when no tests were run has
    453 /// the same result as tuktest_early_skip() except that then no reason
    454 /// for the skipping can be printed.
    455 #define tuktest_early_skip(...) \
    456 do { \
    457 	printf("%s [%s:%u] ", \
    458 			TUKTEST_TAP ? "1..0 # SKIP" : TUKTEST_STR_SKIP, \
    459 			tuktest_basename(__FILE__), __LINE__); \
    460 	printf(__VA_ARGS__); \
    461 	printf("\n"); \
    462 	if (!TUKTEST_TAP && !TUKTEST_QUIET) \
    463 		printf("=== END ===\n"); \
    464 	tuktest_catch_stdout_errors(); \
    465 	exit(TUKTEST_TAP ? EXIT_SUCCESS : TUKTEST_EXIT_SKIP); \
    466 } while (0)
    467 
    468 
    469 /// Some test programs need to do initializations before or between
    470 /// calls to tuktest_run(). If such initializations unexpectedly fail,
    471 /// tuktest_error() can be used to report it as a hard error outside
    472 /// test functions, for example, in main(). Then the remaining tests
    473 /// won't be run (this macro calls exit()).
    474 ///
    475 /// Typically tuktest_error() would be used before any tuktest_run()
    476 /// calls but it is also possible to use tuktest_error() after one or
    477 /// more tests have been run with tuktest_run(). This is in contrast to
    478 /// tuktest_early_skip() which must never be called after tuktest_run().
    479 ///
    480 /// NOTE: tuktest_start() must have been called before tuktest_error().
    481 ///
    482 /// NOTE: This macro can be called from test functions running under
    483 /// tuktest_run() but assert_error() is somewhat preferred in that context.
    484 #define tuktest_error(...) tuktest_error_impl(__FILE__, __LINE__, __VA_ARGS__)
    485 
    486 
    487 /// At the end of main() one should have "return tuktest_end();" which
    488 /// prints the stats or the TAP plan, and handles the exit status.
    489 /// Using exit(tuktest_end()) is OK too.
    490 ///
    491 /// If the test program can detect early that all tests must be skipped,
    492 /// then tuktest_early_skip() may be useful so that the reason why the
    493 /// tests were skipped can be printed.
    494 static int
    495 tuktest_end(void)
    496 {
    497 	tuktest_free_all(&tuktest_malloc_test);
    498 	tuktest_free_all(&tuktest_malloc_global);
    499 
    500 	unsigned total_tests = 0;
    501 	for (unsigned i = 0; i <= TUKTEST_ERROR; ++i)
    502 		total_tests += tuktest_stats[i];
    503 
    504 	if (tuktest_stats[TUKTEST_ERROR] == 0 && tuktest_argc > 1
    505 			&& (unsigned)(tuktest_argc - 1) > total_tests) {
    506 		printf(TUKTEST_STR_ERROR " Fewer tests were run than "
    507 				"specified on the command line. "
    508 				"Was a test name mistyped?\n");
    509 		++tuktest_stats[TUKTEST_ERROR];
    510 	}
    511 
    512 #if TUKTEST_TAP
    513 	// Print the plan only if no "Bail out!" has occurred.
    514 	// Print the skip directive if no tests were run.
    515 	// We cannot know the reason for the skip here though
    516 	// (see tuktest_early_skip()).
    517 	if (tuktest_stats[TUKTEST_ERROR] == 0)
    518 		printf("1..%u%s\n", total_tests,
    519 				total_tests == 0 ? " # SKIP" : "");
    520 
    521 	tuktest_catch_stdout_errors();
    522 	return EXIT_SUCCESS;
    523 #else
    524 	if (!TUKTEST_QUIET)
    525 		printf("---\n"
    526 				"%s# TOTAL: %u" TUKTEST_COLOR_OFF "\n"
    527 				"%s# PASS:  %u" TUKTEST_COLOR_OFF "\n"
    528 				"%s# SKIP:  %u" TUKTEST_COLOR_OFF "\n"
    529 				"%s# FAIL:  %u" TUKTEST_COLOR_OFF "\n"
    530 				"%s# ERROR: %u" TUKTEST_COLOR_OFF "\n"
    531 				"=== END ===\n",
    532 				TUKTEST_COLOR_TOTAL,
    533 				total_tests,
    534 				TUKTEST_COLOR_IF(
    535 					tuktest_stats[TUKTEST_PASS] > 0,
    536 					TUKTEST_COLOR_PASS),
    537 				tuktest_stats[TUKTEST_PASS],
    538 				TUKTEST_COLOR_IF(
    539 					tuktest_stats[TUKTEST_SKIP] > 0,
    540 					TUKTEST_COLOR_SKIP),
    541 				tuktest_stats[TUKTEST_SKIP],
    542 				TUKTEST_COLOR_IF(
    543 					tuktest_stats[TUKTEST_FAIL] > 0,
    544 					TUKTEST_COLOR_FAIL),
    545 				tuktest_stats[TUKTEST_FAIL],
    546 				TUKTEST_COLOR_IF(
    547 					tuktest_stats[TUKTEST_ERROR] > 0,
    548 					TUKTEST_COLOR_ERROR),
    549 				tuktest_stats[TUKTEST_ERROR]);
    550 
    551 	tuktest_catch_stdout_errors();
    552 
    553 	if (tuktest_stats[TUKTEST_ERROR] > 0)
    554 		return TUKTEST_EXIT_ERROR;
    555 
    556 	if (tuktest_stats[TUKTEST_FAIL] > 0)
    557 		return TUKTEST_EXIT_FAIL;
    558 
    559 	if (tuktest_stats[TUKTEST_SKIP] > 0 || total_tests == 0)
    560 		return TUKTEST_EXIT_SKIP;
    561 
    562 	return TUKTEST_EXIT_PASS;
    563 #endif
    564 }
    565 
    566 
    567 /// Runs the specified test function. Requires that tuktest_start()
    568 /// has already been called and that tuktest_end() has NOT been called yet.
    569 #define tuktest_run(testfunc) \
    570 	tuktest_run_test(&(testfunc), #testfunc)
    571 
    572 tuktest_maybe_unused
    573 static void
    574 tuktest_run_test(void (*testfunc)(void), const char *testfunc_str)
    575 {
    576 	// If any command line arguments were given, only the test functions
    577 	// named on the command line will be run.
    578 	if (tuktest_argc > 1) {
    579 		int i = 1;
    580 		while (strcmp(tuktest_argv[i], testfunc_str) != 0)
    581 			if (++i == tuktest_argc)
    582 				return;
    583 	}
    584 
    585 	// This is set so that failed assertions can print the correct
    586 	// test name even when the assertion is in a helper function
    587 	// called by the test function.
    588 	tuktest_name = testfunc_str;
    589 
    590 	// The way setjmp() may be called is very restrictive.
    591 	// A switch statement is one of the few conforming ways
    592 	// to get the value passed to longjmp(); doing something
    593 	// like "int x = setjmp(env)" is NOT allowed (undefined behavior).
    594 	switch (setjmp(tuktest_jmpenv)) {
    595 		case 0:
    596 			testfunc();
    597 			++tuktest_stats[TUKTEST_PASS];
    598 			if (!TUKTEST_QUIET)
    599 				printf(TUKTEST_STR_PASS " %s\n", tuktest_name);
    600 			break;
    601 
    602 		case TUKTEST_FAIL:
    603 			++tuktest_stats[TUKTEST_FAIL];
    604 			break;
    605 
    606 		case TUKTEST_SKIP:
    607 			++tuktest_stats[TUKTEST_SKIP];
    608 			break;
    609 
    610 		default:
    611 			++tuktest_stats[TUKTEST_ERROR];
    612 			exit(tuktest_end());
    613 	}
    614 
    615 	tuktest_free_all(&tuktest_malloc_test);
    616 	tuktest_name = NULL;
    617 }
    618 
    619 
    620 // Maximum allowed file size in tuktest_file_from_* macros and functions.
    621 #ifndef TUKTEST_FILE_SIZE_MAX
    622 #	define TUKTEST_FILE_SIZE_MAX (64L << 20)
    623 #endif
    624 
    625 /// Allocates memory and reads the specified file into a buffer.
    626 /// If the environment variable srcdir is set, it will be prefixed
    627 /// to the filename. Otherwise the filename is used as is (and so
    628 /// the behavior is identical to tuktest_file_from_builddir() below).
    629 ///
    630 /// On success the a pointer to malloc'ed memory is returned.
    631 /// The size of the allocation and the file is stored in *size.
    632 ///
    633 /// If anything goes wrong, a hard error is reported and this function
    634 /// won't return. Possible other tests won't be run (this will call exit()).
    635 ///
    636 /// Empty files and files over TUKTEST_FILE_SIZE_MAX are rejected.
    637 /// The assumption is that something is wrong in these cases.
    638 ///
    639 /// This function can be called either from outside the tests (like in main())
    640 /// or from tests run via tuktest_run(). Remember to free() the memory to
    641 /// keep Valgrind happy.
    642 #define tuktest_file_from_srcdir(filename, sizeptr) \
    643 	tuktest_file_from_x(getenv("srcdir"), filename, sizeptr, \
    644 			__FILE__, __LINE__)
    645 
    646 /// Like tuktest_file_from_srcdir except this reads from the current directory.
    647 #define tuktest_file_from_builddir(filename, sizeptr) \
    648 	tuktest_file_from_x(NULL, filename, sizeptr, __FILE__, __LINE__)
    649 
    650 // Internal helper for the macros above.
    651 tuktest_maybe_unused
    652 static void *
    653 tuktest_file_from_x(const char *prefix, const char *filename, size_t *size,
    654 		const char *prog_filename, unsigned prog_line)
    655 {
    656 	// If needed: buffer for holding prefix + '/' + filename + '\0'.
    657 	char *alloc_name = NULL;
    658 
    659 	// Buffer for the data read from the file.
    660 	void *buf = NULL;
    661 
    662 	// File being read
    663 	FILE *f = NULL;
    664 
    665 	// Error message to use under the "error:" label.
    666 	const char *error_msg = NULL;
    667 
    668 	if (filename == NULL) {
    669 		error_msg = "Filename is NULL";
    670 		filename = "(NULL)";
    671 		goto error;
    672 	}
    673 
    674 	if (filename[0] == '\0') {
    675 		error_msg = "Filename is an empty string";
    676 		filename = "(empty string)";
    677 		goto error;
    678 	}
    679 
    680 	if (size == NULL) {
    681 		error_msg = "The size argument is NULL";
    682 		goto error;
    683 	}
    684 
    685 	// If a prefix was given, construct the full filename.
    686 	if (prefix != NULL && prefix[0] != '\0') {
    687 		const size_t prefix_len = strlen(prefix);
    688 		const size_t filename_len = strlen(filename);
    689 
    690 		const size_t alloc_name_size
    691 				= prefix_len + 1 + filename_len + 1;
    692 		alloc_name = tuktest_malloc_impl(alloc_name_size,
    693 				prog_filename, prog_line);
    694 
    695 		memcpy(alloc_name, prefix, prefix_len);
    696 		alloc_name[prefix_len] = '/';
    697 		memcpy(alloc_name + prefix_len + 1, filename, filename_len);
    698 		alloc_name[prefix_len + 1 + filename_len] = '\0';
    699 
    700 		// Set filename to point to the new string. alloc_name
    701 		// can be freed unconditionally as it is NULL if a prefix
    702 		// wasn't specified.
    703 		filename = alloc_name;
    704 	}
    705 
    706 	f = fopen(filename, "rb");
    707 	if (f == NULL) {
    708 		error_msg = "Failed to open the file";
    709 		goto error;
    710 	}
    711 
    712 	// Get the size of the file and store it in *size.
    713 	//
    714 	// We assume that the file isn't big and even reject very big files.
    715 	// There is no need to use fseeko/ftello from POSIX to support
    716 	// large files. Using standard C functions is portable outside POSIX.
    717 	if (fseek(f, 0, SEEK_END) != 0) {
    718 		error_msg = "Seeking failed (fseek end)";
    719 		goto error;
    720 	}
    721 
    722 	const long end = ftell(f);
    723 	if (end < 0) {
    724 		error_msg = "Seeking failed (ftell)";
    725 		goto error;
    726 	}
    727 
    728 	if (end == 0) {
    729 		error_msg = "File is empty";
    730 		goto error;
    731 	}
    732 
    733 	if (end > TUKTEST_FILE_SIZE_MAX) {
    734 		error_msg = "File size exceeds TUKTEST_FILE_SIZE_MAX";
    735 		goto error;
    736 	}
    737 
    738 	*size = (size_t)end;
    739 	rewind(f);
    740 
    741 	buf = tuktest_malloc_impl(*size, prog_filename, prog_line);
    742 
    743 	const size_t amount = fread(buf, 1, *size, f);
    744 	if (ferror(f)) {
    745 		error_msg = "Read error";
    746 		goto error;
    747 	}
    748 
    749 	if (amount != *size) {
    750 		error_msg = "File is smaller than indicated by ftell()";
    751 		goto error;
    752 	}
    753 
    754 	const int fclose_ret = fclose(f);
    755 	f = NULL;
    756 	if (fclose_ret != 0) {
    757 		error_msg = "Error closing the file";
    758 		goto error;
    759 	}
    760 
    761 	tuktest_free(alloc_name);
    762 	return buf;
    763 
    764 error:
    765 	if (f != NULL)
    766 		(void)fclose(f);
    767 
    768 	tuktest_error_impl(prog_filename, prog_line,
    769 			"tuktest_file_from_x: %s: %s\n", filename, error_msg);
    770 }
    771 
    772 
    773 // Internal helper for assert_fail, assert_skip, and assert_error.
    774 #define tuktest_print_and_jump(result, ...) \
    775 do { \
    776 	tuktest_print_result_prefix(result, __FILE__, __LINE__); \
    777 	printf(__VA_ARGS__); \
    778 	printf("\n"); \
    779 	longjmp(tuktest_jmpenv, result); \
    780 } while (0)
    781 
    782 
    783 /// Unconditionally fails the test (non-zero exit status if not using TAP).
    784 /// Execution will continue from the next test.
    785 ///
    786 /// A printf format string is supported.
    787 /// If no extra message is wanted, use "" as the argument.
    788 #define assert_fail(...) tuktest_print_and_jump(TUKTEST_FAIL, __VA_ARGS__)
    789 
    790 
    791 /// Skips the test (exit status 77 if not using TAP).
    792 /// Execution will continue from the next test.
    793 ///
    794 /// If you can detect early that no tests can be run, tuktest_early_skip()
    795 /// might be a better way to skip the test(s). Especially in TAP mode this
    796 /// makes a difference as with assert_skip() it will list a skipped specific
    797 /// test name but with tuktest_early_skip() it will indicate that the whole
    798 /// test program was skipped (with tuktest_early_skip() the TAP plan will
    799 /// indicate zero tests).
    800 ///
    801 /// A printf format string is supported.
    802 /// If no extra message is wanted, use "" as the argument.
    803 #define assert_skip(...) tuktest_print_and_jump(TUKTEST_SKIP, __VA_ARGS__)
    804 
    805 
    806 /// Hard error (exit status 99 if not using TAP).
    807 /// The remaining tests in this program will not be run or reported.
    808 ///
    809 /// A printf format string is supported.
    810 /// If no extra message is wanted, use "" as the argument.
    811 #define assert_error(...) tuktest_print_and_jump(TUKTEST_ERROR, __VA_ARGS__)
    812 
    813 
    814 /// Fails the test if the test expression doesn't evaluate to false.
    815 #define assert_false(test_expr) \
    816 do { \
    817 	if (test_expr) \
    818 		assert_fail("assert_fail: '%s' is true but should be false", \
    819 				#test_expr); \
    820 } while (0)
    821 
    822 
    823 /// Fails the test if the test expression doesn't evaluate to true.
    824 #define assert_true(test_expr) \
    825 do { \
    826 	if (!(test_expr)) \
    827 		assert_fail("assert_true: '%s' is false but should be true", \
    828 				#test_expr); \
    829 } while (0)
    830 
    831 
    832 /// Fails the test if comparing the signed integer expressions using the
    833 /// specified comparison operator evaluates to false. For example,
    834 /// assert_int(foobar(), >=, 0) fails the test if 'foobar() >= 0' isn't true.
    835 /// For good error messages, the first argument should be the test expression
    836 /// and the third argument the reference value (usually a constant).
    837 ///
    838 /// For equality (==) comparison there is a assert_int_eq() which
    839 /// might be more convenient to use.
    840 #define assert_int(test_expr, cmp_op, ref_value) \
    841 do { \
    842 	const tuktest_int v_test_ = (test_expr); \
    843 	const tuktest_int v_ref_ = (ref_value); \
    844 	if (!(v_test_ cmp_op v_ref_)) \
    845 		assert_fail("assert_int: '%s == %" TUKTEST_PRId \
    846 				"' but expected '... %s %" TUKTEST_PRId "'", \
    847 				#test_expr, v_test_, #cmp_op, v_ref_); \
    848 } while (0)
    849 
    850 
    851 /// Like assert_int() but for unsigned integers.
    852 ///
    853 /// For equality (==) comparison there is a assert_uint_eq() which
    854 /// might be more convenient to use.
    855 #define assert_uint(test_expr, cmp_op, ref_value) \
    856 do { \
    857 	const tuktest_uint v_test_ = (test_expr); \
    858 	const tuktest_uint v_ref_ = (ref_value); \
    859 	if (!(v_test_ cmp_op v_ref_)) \
    860 		assert_fail("assert_uint: '%s == %" TUKTEST_PRIu \
    861 				"' but expected '... %s %" TUKTEST_PRIu "'", \
    862 				#test_expr, v_test_, #cmp_op, v_ref_); \
    863 } while (0)
    864 
    865 
    866 /// Fails the test if test expression doesn't equal the expected
    867 /// signed integer value.
    868 #define assert_int_eq(test_expr, ref_value) \
    869 	assert_int(test_expr, ==, ref_value)
    870 
    871 
    872 /// Fails the test if test expression doesn't equal the expected
    873 /// unsigned integer value.
    874 #define assert_uint_eq(test_expr, ref_value) \
    875 	assert_uint(test_expr, ==, ref_value)
    876 
    877 
    878 /// Fails the test if the test expression doesn't equal the expected
    879 /// enumeration value. This is like assert_int_eq() but the error message
    880 /// shows the enumeration constant names instead of their numeric values
    881 /// as long as the values are non-negative and not big.
    882 ///
    883 /// The third argument must be a table of string pointers. A pointer to
    884 /// a pointer doesn't work because this determines the number of elements
    885 /// in the array using sizeof. For example:
    886 ///
    887 ///     const char *my_enum_names[] = { "MY_FOO", "MY_BAR", "MY_BAZ" };
    888 ///     assert_enum_eq(some_func_returning_my_enum(), MY_BAR, my_enum_names);
    889 ///
    890 /// (If the reference value is out of bounds, both values are printed as
    891 /// an integer. If only test expression is out of bounds, it is printed
    892 /// as an integer and the reference as a string. Otherwise both are printed
    893 /// as a string.)
    894 #define assert_enum_eq(test_expr, ref_value, enum_strings) \
    895 do { \
    896 	const tuktest_int v_test_ = (test_expr); \
    897 	const tuktest_int v_ref_ = (ref_value); \
    898 	if (v_test_ != v_ref_) { \
    899 		const int array_len_ = (int)(sizeof(enum_strings) \
    900 				/ sizeof((enum_strings)[0])); \
    901 		if (v_ref_ < 0 || v_ref_ >= array_len_) \
    902 			assert_fail("assert_enum_eq: '%s == %" TUKTEST_PRId \
    903 					"' but expected " \
    904 					"'... == %" TUKTEST_PRId "'", \
    905 					#test_expr, v_test_, v_ref_); \
    906 		else if (v_test_ < 0 || v_test_ >= array_len_) \
    907 			assert_fail("assert_enum_eq: '%s == %" TUKTEST_PRId \
    908 					"' but expected '... == %s'", \
    909 					#test_expr, v_test_, \
    910 					(enum_strings)[v_ref_]); \
    911 		else \
    912 			assert_fail("assert_enum_eq: '%s == %s' " \
    913 					"but expected '... = %s'", \
    914 					#test_expr, (enum_strings)[v_test_], \
    915 					(enum_strings)[v_ref_]); \
    916 	} \
    917 } while (0)
    918 
    919 
    920 /// Fails the test if the specified bit isn't set in the test expression.
    921 #define assert_bit_set(test_expr, bit) \
    922 do { \
    923 	const tuktest_uint v_test_ = (test_expr); \
    924 	const unsigned v_bit_ = (bit); \
    925 	const tuktest_uint v_mask_ = (tuktest_uint)1 << v_bit_; \
    926 	if (!(v_test_ & v_mask_)) \
    927 		assert_fail("assert_bit_set: '%s == 0x%" TUKTEST_PRIX \
    928 				"' but bit %u (0x%" TUKTEST_PRIX ") " \
    929 				"is not set", \
    930 				#test_expr, v_test_, v_bit_, v_mask_); \
    931 } while (0)
    932 
    933 
    934 /// Fails the test if the specified bit is set in the test expression.
    935 #define assert_bit_not_set(test_expr, bit) \
    936 do { \
    937 	const tuktest_uint v_test_ = (test_expr); \
    938 	const unsigned v_bit_ = (bit); \
    939 	const tuktest_uint v_mask_ = (tuktest_uint)1 << v_bit_; \
    940 	if (v_test_ & v_mask_) \
    941 		assert_fail("assert_bit_not_set: '%s == 0x%" TUKTEST_PRIX \
    942 				"' but bit %u (0x%" TUKTEST_PRIX ") is set", \
    943 				#test_expr, v_test_, v_bit_, v_mask_); \
    944 } while (0)
    945 
    946 
    947 /// Fails the test if unless all bits that are set in the bitmask are also
    948 /// set in the test expression.
    949 #define assert_bitmask_set(test_expr, mask) \
    950 do { \
    951 	const tuktest_uint v_mask_ = (mask); \
    952 	const tuktest_uint v_test_ = (test_expr) & v_mask_; \
    953 	if (v_test_ != v_mask_) \
    954 		assert_fail("assert_bitmask_set: " \
    955 				"'((%s) & 0x%" TUKTEST_PRIX ") == " \
    956 				"0x%" TUKTEST_PRIX "' but expected " \
    957 				"'... == 0x%" TUKTEST_PRIX "'", \
    958 				#test_expr, v_mask_, v_test_, v_mask_); \
    959 } while (0)
    960 
    961 
    962 /// Fails the test if any of the bits that are set in the bitmask are also
    963 /// set in the test expression.
    964 #define assert_bitmask_not_set(test_expr, mask) \
    965 do { \
    966 	const tuktest_uint v_mask_ = (mask); \
    967 	const tuktest_uint v_test_ = (test_expr) & v_mask_; \
    968 	if (v_test_ != 0) \
    969 		assert_fail("assert_bitmask_not_set: "\
    970 				"'((%s) & 0x%" TUKTEST_PRIX ") == " \
    971 				"0x%" TUKTEST_PRIX "' but expected " \
    972 				"'... == 0'", \
    973 				#test_expr, v_mask_, v_test_); \
    974 } while (0)
    975 
    976 
    977 // Internal helper to add common code for string assertions.
    978 #define tuktest_str_helper1(macro_name, test_expr, ref_value) \
    979 	const char *v_test_ = (test_expr); \
    980 	const char *v_ref_ = (ref_value); \
    981 	if (v_test_ == NULL) \
    982 		assert_fail(macro_name ": Test expression '%s' is NULL", \
    983 				#test_expr); \
    984 	if (v_ref_ == NULL) \
    985 		assert_fail(macro_name ": Reference value '%s' is NULL", \
    986 				#ref_value)
    987 
    988 
    989 // Internal helper to add common code for string assertions and to check
    990 // that the reference value isn't an empty string.
    991 #define tuktest_str_helper2(macro_name, test_expr, ref_value) \
    992 	tuktest_str_helper1(macro_name, test_expr, ref_value); \
    993 	if (v_ref_[0] == '\0') \
    994 		assert_fail(macro_name ": Reference value is an empty string")
    995 
    996 
    997 /// Fails the test if the test expression evaluates to string that doesn't
    998 /// equal to the expected string.
    999 #define assert_str_eq(test_expr, ref_value) \
   1000 do { \
   1001 	tuktest_str_helper1("assert_str_eq", test_expr, ref_value); \
   1002 	if (strcmp(v_ref_, v_test_) != 0) \
   1003 		assert_fail("assert_str_eq: '%s' evaluated to '%s' " \
   1004 				"but expected '%s'", \
   1005 				#test_expr, v_test_, v_ref_); \
   1006 } while (0)
   1007 
   1008 
   1009 /// Fails the test if the test expression evaluates to a string that doesn't
   1010 /// contain the reference value as a substring. Also fails the test if
   1011 /// the reference value is an empty string.
   1012 #define assert_str_contains(test_expr, ref_value) \
   1013 do { \
   1014 	tuktest_str_helper2("assert_str_contains", test_expr, ref_value); \
   1015 	if (strstr(v_test_, v_ref_) == NULL) \
   1016 		assert_fail("assert_str_contains: '%s' evaluated to '%s' " \
   1017 				"which doesn't contain '%s'", \
   1018 				#test_expr, v_test_, v_ref_); \
   1019 } while (0)
   1020 
   1021 
   1022 /// Fails the test if the test expression evaluates to a string that
   1023 /// contains the reference value as a substring. Also fails the test if
   1024 /// the reference value is an empty string.
   1025 #define assert_str_doesnt_contain(test_expr, ref_value) \
   1026 do { \
   1027 	tuktest_str_helper2("assert_str_doesnt_contain", \
   1028 			test_expr, ref_value); \
   1029 	if (strstr(v_test_, v_ref_) != NULL) \
   1030 		assert_fail("assert_str_doesnt_contain: " \
   1031 				"'%s' evaluated to '%s' which contains '%s'", \
   1032 				#test_expr, v_test_, v_ref_); \
   1033 } while (0)
   1034 
   1035 
   1036 /// Fails the test if the first array_size elements of the test array
   1037 /// don't equal to correct_array.
   1038 ///
   1039 /// NOTE: This avoids %zu for portability to very old systems that still
   1040 /// can compile C99 code.
   1041 #define assert_array_eq(test_array, correct_array, array_size) \
   1042 do { \
   1043 	for (size_t i_ = 0; i_ < (array_size); ++i_) \
   1044 		if ((test_array)[i_] != (correct_array)[i_]) \
   1045 			assert_fail("assert_array_eq: " \
   1046 					"%s[%" TUKTEST_PRIu "] != "\
   1047 					"%s[%" TUKTEST_PRIu "] " \
   1048 					"but should be equal", \
   1049 					#test_array, (tuktest_uint)i_, \
   1050 					#correct_array, (tuktest_uint)i_); \
   1051 } while (0)
   1052 
   1053 #endif
   1054