/* SPDX-License-Identifier: MPL-2.0 */ /* * A framework for writing general tests. * * A general test typically consists of two parts, the setup part and the * test part. The setup part contains setup functions that set up the context * for the subsequent tests to run. The setup functions cannot fail, and if they * do, execution is aborted because the subsequent tests will not work as * expected either. The test functions, on the other hand, can fail, and if they * do, they are reported as test failures. * * The framework provides basic utilities for writing general tests: * * - To define a setup function or a test function, FN_SETUP() or FN_TEST() can * be used. These functions are automatically executed in the order of their * definition. Note that the order of execution is _not_ related to whether a * function is a setup function or a test function. * * - Within a setup function, CHECK() can be used to write a setup expression * that must succeed. If the expression fails, a fatal error will be reported * and the execution will be aborted. * * - Within a test function, TEST_SUCC() can be used to write a test expression * that should succeed. If the expression fails, a test failure will be reported * but the execution will continue. * * - The number of successful and failed tests is tracked. When a test function * finishes, a summary sentence is printed describing the number of test * failures. The program will exit with a non-zero code if and only if there is * at least one test failure. */ #include #include #include #include /** Starts the definition of a setup function. */ #define FN_SETUP(name) \ void setup_##name(void) __attribute__((constructor(__LINE__ + 200))); \ \ void setup_##name(void) \ { /** Ends the definition of a setup function. */ #define END_SETUP() } #define __CHECK(func, cond) \ errno = 0; \ long _ret = (func); \ if (!(cond)) { \ fprintf(stderr, \ "fatal error: %s: `" #cond "` is false after `" #func \ "` [got %s]\n", \ __func__, strerror(errno)); \ exit(EXIT_FAILURE); \ } /** * Makes a function call and checks its return value is positive. * * The execution will be aborted if the check fails. */ #define CHECK(func) \ ({ \ __CHECK(func, _ret >= 0); \ _ret; \ }) /** * Makes a function call and checks its result with the specified condition. * * The execution will be aborted if the check fails. * * The return value of the function can be accessed with a local variable named * _ret. */ #define CHECK_WITH(func, cond) \ ({ \ __CHECK(func, cond); \ _ret; \ }) static int __total_failures; /** Starts the definition of a test function. */ #define FN_TEST(name) \ void test_##name(void) __attribute__((constructor(__LINE__ + 200))); \ \ void test_##name(void) \ { \ int __tests_passed = 0, __tests_failed = 0; /** Ends the definition of a test function. */ #define END_TEST() \ fprintf(stderr, "%s summary: %d tests passed, %d tests failed\n", \ __func__, __tests_passed, __tests_failed); \ __total_failures += __tests_failed; \ } #define __TEST(func, err, cond) \ errno = 0; \ long _ret = (func); \ if (errno != (err)) { \ __tests_failed++; \ fprintf(stderr, \ "%s: `" #func "` failed [got %s, but expected %s]\n", \ __func__, strerror(errno), strerror(err)); \ } else if (!(cond)) { \ __tests_failed++; \ fprintf(stderr, \ "%s: `" #func "` failed [got %s, but `" #cond \ "` is false]\n", \ __func__, strerror(errno)); \ } else { \ __tests_passed++; \ fprintf(stderr, "%s: `" #func "` passed [got %s]\n", __func__, \ strerror(errno)); \ } /** * Makes a function call and checks its result. * * A test failure will be reported if the function does not set the specified * errno or the specified condition does not meet. * * The return value of the function can be accessed with a local variable named * _ret. */ #define TEST(func, err, cond) \ ({ \ __TEST(func, err, cond); \ _ret; \ }) /** * Makes a function call and checks whether it succeeds. * * A test failure will be reported if the function sets a non-zero errno. */ #define TEST_SUCC(func) TEST(func, 0, 1) /** * Makes a function call and checks whether it fails in the specified way. * * A test failure will be reported if the function does not set the specified * errno. */ #define TEST_ERRNO(func, err) TEST(func, err, 1) /** * Makes a function call and checks whether it produces expected results. * * A test failure will be reported if the function sets a non-zero errno or the * specified condition does not meet. * * The return value of the function can be accessed with a local variable named * _ret. */ #define TEST_RES(func, cond) TEST(func, 0, cond) int main(void) { return __total_failures ? 1 : 0; }