1 // SPDX-License-Identifier: 0BSD
3 ///////////////////////////////////////////////////////////////////////////////
6 /// \brief Helper macros for writing simple test programs
7 /// \version 2024-02-14
9 /// Some inspiration was taken from Seatest by Keith Nicholas and
10 /// from STest which is a fork of Seatest by Jia Tan.
12 /// This is standard C99/C11 only and thus should be fairly portable
13 /// outside POSIX systems too.
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:
21 /// - Hard error (the remaining tests, if any, are not run)
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.
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.
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.
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.
50 /// If TUKTEST_TAP is used, TUKTEST_QUIET and TUKTEST_COLOR are ignored.
52 /// The main() function may look like this (remember to include config.h
53 /// or such files too if needed!):
55 /// #include "tuktest.h"
57 /// int main(int argc, char **argv)
59 /// tuktest_start(argc, argv);
61 /// if (!is_package_foo_available())
62 /// tuktest_early_skip("Optional package foo is not available");
64 /// if (!do_common_initializations())
65 /// tuktest_error("Error during common initializations");
67 /// tuktest_run(testfunc1);
68 /// tuktest_run(testfunc2);
70 /// return tuktest_end();
73 /// Using exit(tuktest_end()) as a pair to tuktest_start() is OK too.
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.
83 /// Search for "define assert" in this file to find the explanations
84 /// of the available assertion macros.
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.)
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
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.)
105 /// - Everything else is for internal use only.
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
113 /// Meson: https://mesonbuild.com/Unit-tests.html
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:
119 /// set_tests_properties(foo_test_name PROPERTIES SKIP_RETURN_CODE 77)
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
125 // Author: Lasse Collin
127 ///////////////////////////////////////////////////////////////////////////////
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.
138 # include <inttypes.h>
147 #if defined(__GNUC__) && defined(__GNUC_MINOR__)
148 # define TUKTEST_GNUC_REQ(major, minor) \
149 ((__GNUC__ == (major) && __GNUC_MINOR__ >= (minor)) \
150 || __GNUC__ > (major))
152 # define TUKTEST_GNUC_REQ(major, minor) 0
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__))
161 # define tuktest_maybe_unused
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"
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
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
186 enum tuktest_result
{
195 # undef TUKTEST_QUIET
196 # undef TUKTEST_COLOR
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!"
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) : "" )
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) ""
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)
233 // NOTE: If TUKTEST_TAP is defined then TUKTEST_QUIET will get undefined above.
234 #ifndef TUKTEST_QUIET
235 # define TUKTEST_QUIET 0
237 # undef TUKTEST_QUIET
238 # define TUKTEST_QUIET 1
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 };
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
;
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
;
255 // longjmp() target for when a test-assertion fails.
256 static jmp_buf tuktest_jmpenv
;
259 // This declaration is needed for tuktest_malloc().
260 static int tuktest_end(void);
263 // Internal helper for handling hard errors both inside and
264 // outside tuktest_run().
265 #define tuktest_error_impl(filename, line, ...) \
267 tuktest_print_result_prefix(TUKTEST_ERROR, filename, line); \
268 printf(__VA_ARGS__); \
270 ++tuktest_stats[TUKTEST_ERROR]; \
271 exit(tuktest_end()); \
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.
278 tuktest_catch_stdout_errors(void)
280 if (ferror(stdout
) || fclose(stdout
)) {
281 fputs("Error while writing to stdout\n", stderr
);
282 exit(TUKTEST_EXIT_ERROR
);
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.
292 tuktest_basename(const char *filename
)
294 for (const char *p
= filename
+ strlen(filename
); p
> filename
; --p
)
295 if (*p
== '/' || *p
== '\\')
302 // Internal helper that prints the prefix of the fail/skip/error message line.
304 tuktest_print_result_prefix(enum tuktest_result result
,
305 const char *filename
, unsigned line
)
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
314 const char *short_filename
= tuktest_basename(filename
);
316 if (tuktest_name
!= NULL
)
317 printf("%s %s [%s:%u] ", result_str
, tuktest_name
,
318 short_filename
, line
);
320 printf("%s [%s:%u] ", result_str
, short_filename
, line
);
324 // An entry for linked list of memory allocations.
325 struct tuktest_malloc_record
{
326 struct tuktest_malloc_record
*next
;
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
;
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
;
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
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__)
350 tuktest_malloc_impl(size_t size
, const char *filename
, unsigned line
)
352 void *p
= malloc(size
== 0 ? 1 : size
);
353 struct tuktest_malloc_record
*r
= malloc(sizeof(*r
));
355 if (p
== NULL
|| r
== NULL
) {
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",
368 if (tuktest_name
== NULL
) {
369 // We were called outside tuktest_run().
370 r
->next
= tuktest_malloc_global
;
371 tuktest_malloc_global
= r
;
373 // We were called under tuktest_run().
374 r
->next
= tuktest_malloc_test
;
375 tuktest_malloc_test
= r
;
382 /// Frees memory allocated using tuktest_malloc(). Usually this isn't needed
383 /// as the memory is freed automatically.
385 /// NULL is silently ignored.
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__)
393 tuktest_free_impl(void *p
, const char *filename
, unsigned line
)
398 struct tuktest_malloc_record
**r
= tuktest_name
!= NULL
399 ? &tuktest_malloc_test
: &tuktest_malloc_global
;
402 struct tuktest_malloc_record
*tmp
= *r
;
414 tuktest_error_impl(filename
, line
, "tuktest_free: "
415 "Allocation matching the pointer was not found");
419 // Frees all allocates in the given record list. The argument must be
420 // either &tuktest_malloc_test or &tuktest_malloc_global.
422 tuktest_free_all(struct tuktest_malloc_record
**r
)
425 struct tuktest_malloc_record
*tmp
= *r
;
433 /// Initialize the test framework. No other functions or macros
434 /// from this file may be called before calling this.
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) \
441 tuktest_argc = argc; \
442 tuktest_argv = argv; \
443 if (!TUKTEST_TAP && !TUKTEST_QUIET) \
444 printf("=== %s ===\n", tuktest_basename(__FILE__)); \
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().
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(...) \
457 printf("%s [%s:%u] ", \
458 TUKTEST_TAP ? "1..0 # SKIP" : TUKTEST_STR_SKIP, \
459 tuktest_basename(__FILE__), __LINE__); \
460 printf(__VA_ARGS__); \
462 if (!TUKTEST_TAP && !TUKTEST_QUIET) \
463 printf("=== END ===\n"); \
464 tuktest_catch_stdout_errors(); \
465 exit(TUKTEST_TAP ? EXIT_SUCCESS : TUKTEST_EXIT_SKIP); \
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()).
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().
480 /// NOTE: tuktest_start() must have been called before tuktest_error().
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__)
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.
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.
497 tuktest_free_all(&tuktest_malloc_test
);
498 tuktest_free_all(&tuktest_malloc_global
);
500 unsigned total_tests
= 0;
501 for (unsigned i
= 0; i
<= TUKTEST_ERROR
; ++i
)
502 total_tests
+= tuktest_stats
[i
];
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
];
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" : "");
521 tuktest_catch_stdout_errors();
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"
535 tuktest_stats
[TUKTEST_PASS
] > 0,
537 tuktest_stats
[TUKTEST_PASS
],
539 tuktest_stats
[TUKTEST_SKIP
] > 0,
541 tuktest_stats
[TUKTEST_SKIP
],
543 tuktest_stats
[TUKTEST_FAIL
] > 0,
545 tuktest_stats
[TUKTEST_FAIL
],
547 tuktest_stats
[TUKTEST_ERROR
] > 0,
548 TUKTEST_COLOR_ERROR
),
549 tuktest_stats
[TUKTEST_ERROR
]);
551 tuktest_catch_stdout_errors();
553 if (tuktest_stats
[TUKTEST_ERROR
] > 0)
554 return TUKTEST_EXIT_ERROR
;
556 if (tuktest_stats
[TUKTEST_FAIL
] > 0)
557 return TUKTEST_EXIT_FAIL
;
559 if (tuktest_stats
[TUKTEST_SKIP
] > 0 || total_tests
== 0)
560 return TUKTEST_EXIT_SKIP
;
562 return TUKTEST_EXIT_PASS
;
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)
574 tuktest_run_test(void (*testfunc
)(void), const char *testfunc_str
)
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) {
580 while (strcmp(tuktest_argv
[i
], testfunc_str
) != 0)
581 if (++i
== tuktest_argc
)
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
;
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
)) {
597 ++tuktest_stats
[TUKTEST_PASS
];
599 printf(TUKTEST_STR_PASS
" %s\n", tuktest_name
);
603 ++tuktest_stats
[TUKTEST_FAIL
];
607 ++tuktest_stats
[TUKTEST_SKIP
];
611 ++tuktest_stats
[TUKTEST_ERROR
];
615 tuktest_free_all(&tuktest_malloc_test
);
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)
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).
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.
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()).
636 /// Empty files and files over TUKTEST_FILE_SIZE_MAX are rejected.
637 /// The assumption is that something is wrong in these cases.
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, \
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__)
650 // Internal helper for the macros above.
653 tuktest_file_from_x(const char *prefix
, const char *filename
, size_t *size
,
654 const char *prog_filename
, unsigned prog_line
)
656 // If needed: buffer for holding prefix + '/' + filename + '\0'.
657 char *alloc_name
= NULL
;
659 // Buffer for the data read from the file.
665 // Error message to use under the "error:" label.
666 const char *error_msg
= NULL
;
668 if (filename
== NULL
) {
669 error_msg
= "Filename is NULL";
674 if (filename
[0] == '\0') {
675 error_msg
= "Filename is an empty string";
676 filename
= "(empty string)";
681 error_msg
= "The size argument is NULL";
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
);
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
);
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';
700 // Set filename to point to the new string. alloc_name
701 // can be freed unconditionally as it is NULL if a prefix
703 filename
= alloc_name
;
706 f
= fopen(filename
, "rb");
708 error_msg
= "Failed to open the file";
712 // Get the size of the file and store it in *size.
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)";
722 const long end
= ftell(f
);
724 error_msg
= "Seeking failed (ftell)";
729 error_msg
= "File is empty";
733 if (end
> TUKTEST_FILE_SIZE_MAX
) {
734 error_msg
= "File size exceeds TUKTEST_FILE_SIZE_MAX";
741 buf
= tuktest_malloc_impl(*size
, prog_filename
, prog_line
);
743 const size_t amount
= fread(buf
, 1, *size
, f
);
745 error_msg
= "Read error";
749 if (amount
!= *size
) {
750 error_msg
= "File is smaller than indicated by ftell()";
754 const int fclose_ret
= fclose(f
);
756 if (fclose_ret
!= 0) {
757 error_msg
= "Error closing the file";
761 tuktest_free(alloc_name
);
768 tuktest_error_impl(prog_filename
, prog_line
,
769 "tuktest_file_from_x: %s: %s\n", filename
, error_msg
);
773 // Internal helper for assert_fail, assert_skip, and assert_error.
774 #define tuktest_print_and_jump(result, ...) \
776 tuktest_print_result_prefix(result, __FILE__, __LINE__); \
777 printf(__VA_ARGS__); \
779 longjmp(tuktest_jmpenv, result); \
783 /// Unconditionally fails the test (non-zero exit status if not using TAP).
784 /// Execution will continue from the next test.
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__)
791 /// Skips the test (exit status 77 if not using TAP).
792 /// Execution will continue from the next test.
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).
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__)
806 /// Hard error (exit status 99 if not using TAP).
807 /// The remaining tests in this program will not be run or reported.
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__)
814 /// Fails the test if the test expression doesn't evaluate to false.
815 #define assert_false(test_expr) \
818 assert_fail("assert_fail: '%s' is true but should be false", \
823 /// Fails the test if the test expression doesn't evaluate to true.
824 #define assert_true(test_expr) \
827 assert_fail("assert_true: '%s' is false but should be true", \
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).
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) \
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_); \
851 /// Like assert_int() but for unsigned integers.
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) \
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_); \
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)
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)
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.
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:
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);
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
894 #define assert_enum_eq(test_expr, ref_value, enum_strings) \
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 \
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_]); \
912 assert_fail("assert_enum_eq: '%s == %s' " \
913 "but expected '... = %s'", \
914 #test_expr, (enum_strings)[v_test_], \
915 (enum_strings)[v_ref_]); \
920 /// Fails the test if the specified bit isn't set in the test expression.
921 #define assert_bit_set(test_expr, bit) \
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 ") " \
930 #test_expr, v_test_, v_bit_, v_mask_); \
934 /// Fails the test if the specified bit is set in the test expression.
935 #define assert_bit_not_set(test_expr, bit) \
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_); \
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) \
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_); \
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) \
966 const tuktest_uint v_mask_ = (mask); \
967 const tuktest_uint v_test_ = (test_expr) & v_mask_; \
969 assert_fail("assert_bitmask_not_set: "\
970 "'((%s) & 0x%" TUKTEST_PRIX ") == " \
971 "0x%" TUKTEST_PRIX "' but expected " \
973 #test_expr, v_mask_, v_test_); \
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", \
984 if (v_ref_ == NULL) \
985 assert_fail(macro_name ": Reference value '%s' is NULL", \
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")
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) \
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_); \
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) \
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_); \
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) \
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_); \
1036 /// Fails the test if the first array_size elements of the test array
1037 /// don't equal to correct_array.
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) \
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_); \