![]() |
Parolin 0.7.9 6796
Console (soon DLLs) to do a tar like job
|
Helper macros for writing simple test programs. More...
#include <stddef.h>
#include <inttypes.h>
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
Go to the source code of this file.
Data Structures | |
struct | tuktest_malloc_record |
Macros | |
#define | TUKTEST_GNUC_REQ(major, minor) |
#define | tuktest_maybe_unused |
#define | TUKTEST_PRId PRId64 |
#define | TUKTEST_PRIu PRIu64 |
#define | TUKTEST_PRIX PRIX64 |
#define | TUKTEST_EXIT_PASS EXIT_SUCCESS |
#define | TUKTEST_EXIT_FAIL EXIT_FAILURE |
#define | TUKTEST_EXIT_SKIP 77 |
#define | TUKTEST_EXIT_ERROR 99 |
#define | TUKTEST_TAP 0 |
#define | TUKTEST_COLOR_PASS "" |
#define | TUKTEST_COLOR_FAIL "" |
#define | TUKTEST_COLOR_SKIP "" |
#define | TUKTEST_COLOR_ERROR "" |
#define | TUKTEST_COLOR_TOTAL "" |
#define | TUKTEST_COLOR_OFF "" |
#define | TUKTEST_COLOR_IF(cond, color) |
#define | TUKTEST_COLOR_ADD(str, color) |
#define | TUKTEST_STR_PASS TUKTEST_COLOR_ADD("PASS:", TUKTEST_COLOR_PASS) |
#define | TUKTEST_STR_FAIL TUKTEST_COLOR_ADD("FAIL:", TUKTEST_COLOR_FAIL) |
#define | TUKTEST_STR_SKIP TUKTEST_COLOR_ADD("SKIP:", TUKTEST_COLOR_SKIP) |
#define | TUKTEST_STR_ERROR TUKTEST_COLOR_ADD("ERROR:", TUKTEST_COLOR_ERROR) |
#define | TUKTEST_QUIET 0 |
#define | tuktest_error_impl(filename, line, ...) |
#define | tuktest_malloc(size) |
#define | tuktest_free(ptr) |
#define | tuktest_start(argc, argv) |
#define | tuktest_early_skip(...) |
#define | tuktest_error(...) |
#define | tuktest_run(testfunc) |
#define | TUKTEST_FILE_SIZE_MAX (64L << 20) |
#define | tuktest_file_from_srcdir(filename, sizeptr) |
#define | tuktest_file_from_builddir(filename, sizeptr) |
Like tuktest_file_from_srcdir except this reads from the current directory. | |
#define | tuktest_print_and_jump(result, ...) |
#define | assert_fail(...) |
#define | assert_skip(...) |
#define | assert_error(...) |
#define | assert_false(test_expr) |
Fails the test if the test expression doesn't evaluate to false. | |
#define | assert_true(test_expr) |
Fails the test if the test expression doesn't evaluate to true. | |
#define | assert_int(test_expr, cmp_op, ref_value) |
#define | assert_uint(test_expr, cmp_op, ref_value) |
#define | assert_int_eq(test_expr, ref_value) |
#define | assert_uint_eq(test_expr, ref_value) |
#define | assert_enum_eq(test_expr, ref_value, enum_strings) |
#define | assert_bit_set(test_expr, bit) |
Fails the test if the specified bit isn't set in the test expression. | |
#define | assert_bit_not_set(test_expr, bit) |
Fails the test if the specified bit is set in the test expression. | |
#define | assert_bitmask_set(test_expr, mask) |
#define | assert_bitmask_not_set(test_expr, mask) |
#define | tuktest_str_helper1(macro_name, test_expr, ref_value) |
#define | tuktest_str_helper2(macro_name, test_expr, ref_value) |
#define | assert_str_eq(test_expr, ref_value) |
#define | assert_str_contains(test_expr, ref_value) |
#define | assert_str_doesnt_contain(test_expr, ref_value) |
#define | assert_array_eq(test_array, correct_array, array_size) |
Enumerations | |
enum | tuktest_result { TUKTEST_PASS , TUKTEST_FAIL , TUKTEST_SKIP , TUKTEST_ERROR } |
Helper macros for writing simple test programs.
Some inspiration was taken from Seatest by Keith Nicholas and from STest which is a fork of Seatest by Jia Tan.
This is standard C99/C11 only and thus should be fairly portable outside POSIX systems too.
This supports putting multiple tests in a single test program although it is perfectly fine to have only one test per program. Each test can produce one of these results:
By default this produces an exit status that is compatible with Automake and Meson, and mostly compatible with CMake.[1] If a test program contains multiple tests, only one exit code can be returned. Of the following, the first match is used:
A summary of tests being run and their results are printed to stdout. If you want ANSI coloring for the output, #define TUKTEST_COLOR. If you only want output when something goes wrong, #define TUKTEST_QUIET.
The downside of the above mapping is that it cannot indicate if some tests were skipped and some passed. If that is likely to happen it may be better to split into multiple test programs (one test per program) or use the TAP mode described below.
By using #define TUKTEST_TAP before #including this file the output will be Test Anything Protocol (TAP) version 12 compatible and the exit status will always be EXIT_SUCCESS. This can be easily used with Automake via its tap-driver.sh. Meson supports TAP natively. TAP's todo-directive isn't supported for now, mostly because it's not trivially convertible to the exit-status reporting method.
If TUKTEST_TAP is used, TUKTEST_QUIET and TUKTEST_COLOR are ignored.
The main() function may look like this (remember to include config.h or such files too if needed!):
#include "tuktest.h" int main(int argc, char **argv) { tuktest_start(argc, argv); if (!is_package_foo_available()) tuktest_early_skip("Optional package foo is not available"); if (!do_common_initializations()) tuktest_error("Error during common initializations"); tuktest_run(testfunc1); tuktest_run(testfunc2); return tuktest_end(); }
Using exit(tuktest_end()) as a pair to tuktest_start() is OK too.
Each test function called via tuktest_run() should be of type "void testfunc1(void)". The test functions should use the various assert_CONDITION() macros. The current test stops if an assertion fails (this is implemented with setjmp/longjmp). Execution continues from the next test unless the failure was due to assert_error() (indicating a hard error) which makes the program exit() without running any remaining tests.
Search for "define assert" in this file to find the explanations of the available assertion macros.
IMPORTANT:
Footnotes:
[1] As of 2022-06-02: See the Automake manual "info (automake)Scripts-based Testsuites" or: https://www.gnu.org/software/automake/manual/automake.html#Scripts_002dbased-Testsuites
Meson: https://mesonbuild.com/Unit-tests.html
CMake handles passing and failing tests by default but treats hard errors as regular fails. To make CMake support skipped tests correctly, one has to set the SKIP_RETURN_CODE property for each test:
set_tests_properties(foo_test_name PROPERTIES SKIP_RETURN_CODE 77)
See: https://cmake.org/cmake/help/latest/command/set_tests_properties.html https://cmake.org/cmake/help/latest/prop_test/SKIP_RETURN_CODE.html
#define assert_array_eq | ( | test_array, | |
correct_array, | |||
array_size ) |
Fails the test if the first array_size elements of the test array don't equal to correct_array.
NOTE: This avoids zu for portability to very old systems that still can compile C99 code.
#define assert_bit_not_set | ( | test_expr, | |
bit ) |
Fails the test if the specified bit is set in the test expression.
#define assert_bit_set | ( | test_expr, | |
bit ) |
Fails the test if the specified bit isn't set in the test expression.
#define assert_bitmask_not_set | ( | test_expr, | |
mask ) |
Fails the test if any of the bits that are set in the bitmask are also set in the test expression.
#define assert_bitmask_set | ( | test_expr, | |
mask ) |
Fails the test if unless all bits that are set in the bitmask are also set in the test expression.
#define assert_enum_eq | ( | test_expr, | |
ref_value, | |||
enum_strings ) |
Fails the test if the test expression doesn't equal the expected enumeration value. This is like assert_int_eq() but the error message shows the enumeration constant names instead of their numeric values as long as the values are non-negative and not big.
The third argument must be a table of string pointers. A pointer to a pointer doesn't work because this determines the number of elements in the array using sizeof. For example:
const char *my_enum_names[] = { "MY_FOO", "MY_BAR", "MY_BAZ" }; assert_enum_eq(some_func_returning_my_enum(), MY_BAR, my_enum_names);
(If the reference value is out of bounds, both values are printed as an integer. If only test expression is out of bounds, it is printed as an integer and the reference as a string. Otherwise both are printed as a string.)
#define assert_error | ( | ... | ) |
Hard error (exit status 99 if not using TAP). The remaining tests in this program will not be run or reported.
A printf format string is supported. If no extra message is wanted, use "" as the argument.
#define assert_fail | ( | ... | ) |
Unconditionally fails the test (non-zero exit status if not using TAP). Execution will continue from the next test.
A printf format string is supported. If no extra message is wanted, use "" as the argument.
#define assert_false | ( | test_expr | ) |
Fails the test if the test expression doesn't evaluate to false.
#define assert_int | ( | test_expr, | |
cmp_op, | |||
ref_value ) |
Fails the test if comparing the signed integer expressions using the specified comparison operator evaluates to false. For example, assert_int(foobar(), >=, 0) fails the test if 'foobar() >= 0' isn't true. For good error messages, the first argument should be the test expression and the third argument the reference value (usually a constant).
For equality (==) comparison there is a assert_int_eq() which might be more convenient to use.
#define assert_int_eq | ( | test_expr, | |
ref_value ) |
Fails the test if test expression doesn't equal the expected signed integer value.
#define assert_skip | ( | ... | ) |
Skips the test (exit status 77 if not using TAP). Execution will continue from the next test.
If you can detect early that no tests can be run, tuktest_early_skip() might be a better way to skip the test(s). Especially in TAP mode this makes a difference as with assert_skip() it will list a skipped specific test name but with tuktest_early_skip() it will indicate that the whole test program was skipped (with tuktest_early_skip() the TAP plan will indicate zero tests).
A printf format string is supported. If no extra message is wanted, use "" as the argument.
#define assert_str_contains | ( | test_expr, | |
ref_value ) |
Fails the test if the test expression evaluates to a string that doesn't contain the reference value as a substring. Also fails the test if the reference value is an empty string.
#define assert_str_doesnt_contain | ( | test_expr, | |
ref_value ) |
Fails the test if the test expression evaluates to a string that contains the reference value as a substring. Also fails the test if the reference value is an empty string.
#define assert_str_eq | ( | test_expr, | |
ref_value ) |
Fails the test if the test expression evaluates to string that doesn't equal to the expected string.
#define assert_true | ( | test_expr | ) |
Fails the test if the test expression doesn't evaluate to true.
#define assert_uint | ( | test_expr, | |
cmp_op, | |||
ref_value ) |
Like assert_int() but for unsigned integers.
For equality (==) comparison there is a assert_uint_eq() which might be more convenient to use.
#define assert_uint_eq | ( | test_expr, | |
ref_value ) |
Fails the test if test expression doesn't equal the expected unsigned integer value.
#define TUKTEST_COLOR_ADD | ( | str, | |
color ) |
#define TUKTEST_COLOR_ERROR "" |
#define TUKTEST_COLOR_FAIL "" |
#define TUKTEST_COLOR_IF | ( | cond, | |
color ) |
#define TUKTEST_COLOR_OFF "" |
#define TUKTEST_COLOR_PASS "" |
#define TUKTEST_COLOR_SKIP "" |
#define TUKTEST_COLOR_TOTAL "" |
#define tuktest_early_skip | ( | ... | ) |
If it can be detected early that no tests can be run, this macro can be called after tuktest_start() but before any tuktest_run() to print a reason why the tests were skipped. Note that this macro calls exit().
Using "return tuktest_end();" in main() when no tests were run has the same result as tuktest_early_skip() except that then no reason for the skipping can be printed.
#define tuktest_error | ( | ... | ) |
Some test programs need to do initializations before or between calls to tuktest_run(). If such initializations unexpectedly fail, tuktest_error() can be used to report it as a hard error outside test functions, for example, in main(). Then the remaining tests won't be run (this macro calls exit()).
Typically tuktest_error() would be used before any tuktest_run() calls but it is also possible to use tuktest_error() after one or more tests have been run with tuktest_run(). This is in contrast to tuktest_early_skip() which must never be called after tuktest_run().
NOTE: tuktest_start() must have been called before tuktest_error().
NOTE: This macro can be called from test functions running under tuktest_run() but assert_error() is somewhat preferred in that context.
#define tuktest_error_impl | ( | filename, | |
line, | |||
... ) |
#define TUKTEST_EXIT_ERROR 99 |
#define TUKTEST_EXIT_FAIL EXIT_FAILURE |
#define TUKTEST_EXIT_PASS EXIT_SUCCESS |
#define TUKTEST_EXIT_SKIP 77 |
#define tuktest_file_from_builddir | ( | filename, | |
sizeptr ) |
Like tuktest_file_from_srcdir except this reads from the current directory.
#define tuktest_file_from_srcdir | ( | filename, | |
sizeptr ) |
Allocates memory and reads the specified file into a buffer. If the environment variable srcdir is set, it will be prefixed to the filename. Otherwise the filename is used as is (and so the behavior is identical to tuktest_file_from_builddir() below).
On success the a pointer to malloc'ed memory is returned. The size of the allocation and the file is stored in *size.
If anything goes wrong, a hard error is reported and this function won't return. Possible other tests won't be run (this will call exit()).
Empty files and files over TUKTEST_FILE_SIZE_MAX are rejected. The assumption is that something is wrong in these cases.
This function can be called either from outside the tests (like in main()) or from tests run via tuktest_run(). Remember to free() the memory to keep Valgrind happy.
#define TUKTEST_FILE_SIZE_MAX (64L << 20) |
#define tuktest_free | ( | ptr | ) |
Frees memory allocated using tuktest_malloc(). Usually this isn't needed as the memory is freed automatically.
NULL is silently ignored.
NOTE: Under tuktest_run() only memory allocated there can be freed. That is, allocations done outside tuktest_run() can only be freed outside tuktest_run().
#define tuktest_malloc | ( | size | ) |
A wrapper for malloc() that never return NULL and the allocated memory is automatically freed at the end of tuktest_run() (if allocation was done within a test) or early in tuktest_end() (if allocation was done outside tuktest_run()).
If allocation fails, a hard error is reported and this function won't return. Possible other tests won't be run (this will call exit()).
#define tuktest_maybe_unused |
#define TUKTEST_PRId PRId64 |
#define tuktest_print_and_jump | ( | result, | |
... ) |
#define TUKTEST_PRIu PRIu64 |
#define TUKTEST_PRIX PRIX64 |
#define TUKTEST_QUIET 0 |
#define tuktest_run | ( | testfunc | ) |
Runs the specified test function. Requires that tuktest_start() has already been called and that tuktest_end() has NOT been called yet.
#define tuktest_start | ( | argc, | |
argv ) |
Initialize the test framework. No other functions or macros from this file may be called before calling this.
If the arguments from main() aren't available, use 0 and NULL. If these are set, then only a subset of tests can be run by specifying their names on the command line.
#define TUKTEST_STR_ERROR TUKTEST_COLOR_ADD("ERROR:", TUKTEST_COLOR_ERROR) |
#define TUKTEST_STR_FAIL TUKTEST_COLOR_ADD("FAIL:", TUKTEST_COLOR_FAIL) |
#define tuktest_str_helper1 | ( | macro_name, | |
test_expr, | |||
ref_value ) |
#define tuktest_str_helper2 | ( | macro_name, | |
test_expr, | |||
ref_value ) |
#define TUKTEST_STR_PASS TUKTEST_COLOR_ADD("PASS:", TUKTEST_COLOR_PASS) |
#define TUKTEST_STR_SKIP TUKTEST_COLOR_ADD("SKIP:", TUKTEST_COLOR_SKIP) |
#define TUKTEST_TAP 0 |
enum tuktest_result |