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