1 /* 2 * Copyright 2022-2023 the Pacemaker project contributors 3 * 4 * The version control history for this file may have further details. 5 * 6 * This source code is licensed under the GNU Lesser General Public License 7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. 8 */ 9 10 #include <signal.h> 11 #include <stdarg.h> 12 #include <stddef.h> 13 #include <stdint.h> 14 #include <setjmp.h> 15 #include <sys/resource.h> 16 #include <sys/types.h> 17 #include <sys/wait.h> 18 #include <unistd.h> 19 20 #include <cmocka.h> 21 22 #ifndef CRM_COMMON_UNITTEST_INTERNAL__H 23 #define CRM_COMMON_UNITTEST_INTERNAL__H 24 25 /* internal unit testing related utilities */ 26 27 /*! 28 * \internal 29 * \brief Assert that a statement aborts through CRM_ASSERT(). 30 * 31 * \param[in] stmt Statement to execute; can be an expression. 32 * 33 * A cmocka-like assert macro for use in unit testing. This one verifies that a 34 * statement aborts through CRM_ASSERT(), erroring out if that is not the case. 35 * 36 * This macro works by running the statement in a forked child process with core 37 * dumps disabled (CRM_ASSERT() calls \c abort(), which will write out a core 38 * dump). The parent waits for the child to exit and checks why. If the child 39 * received a \c SIGABRT, the test passes. For all other cases, the test fails. 40 * 41 * \note If cmocka's expect_*() or will_return() macros are called along with 42 * pcmk__assert_asserts(), they must be called within a block that is 43 * passed as the \c stmt argument. That way, the values are added only to 44 * the child's queue. Otherwise, values added to the parent's queue will 45 * never be popped, and the test will fail. 46 */ 47 #define pcmk__assert_asserts(stmt) \ 48 do { \ 49 pid_t p = fork(); \ 50 if (p == 0) { \ 51 struct rlimit cores = { 0, 0 }; \ 52 setrlimit(RLIMIT_CORE, &cores); \ 53 stmt; \ 54 _exit(0); \ 55 } else if (p > 0) { \ 56 int wstatus = 0; \ 57 if (waitpid(p, &wstatus, 0) == -1) { \ 58 fail_msg("waitpid failed"); \ 59 } \ 60 if (!(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGABRT)) { \ 61 fail_msg("statement terminated in child without asserting"); \ 62 } \ 63 } else { \ 64 fail_msg("unable to fork for assert test"); \ 65 } \ 66 } while (0); 67 68 /*! 69 * \internal 70 * \brief Assert that a statement exits with the expected exit status. 71 * 72 * \param[in] stmt Statement to execute; can be an expression. 73 * \param[in] rc The expected exit status. 74 * 75 * This functions just like \c pcmk__assert_asserts, except that it tests for 76 * an expected exit status. Abnormal termination or incorrect exit status is 77 * treated as a failure of the test. 78 * 79 * In the event that stmt does not exit at all, the special code \c CRM_EX_NONE 80 * will be returned. It is expected that this code is not used anywhere, thus 81 * always causing an error. 82 */ 83 #define pcmk__assert_exits(rc, stmt) \ 84 do { \ 85 pid_t p = fork(); \ 86 if (p == 0) { \ 87 struct rlimit cores = { 0, 0 }; \ 88 setrlimit(RLIMIT_CORE, &cores); \ 89 stmt; \ 90 _exit(CRM_EX_NONE); \ 91 } else if (p > 0) { \ 92 int wstatus = 0; \ 93 if (waitpid(p, &wstatus, 0) == -1) { \ 94 fail_msg("waitpid failed"); \ 95 } \ 96 if (!WIFEXITED(wstatus)) { \ 97 fail_msg("statement terminated abnormally"); \ 98 } else if (WEXITSTATUS(wstatus) != rc) { \ 99 fail_msg("statement exited with %d, not expected %d", WEXITSTATUS(wstatus), rc); \ 100 } \ 101 } else { \ 102 fail_msg("unable to fork for assert test"); \ 103 } \ 104 } while (0); 105 106 /* Generate the main function of most unit test files. Typically, group_setup 107 * and group_teardown will be NULL. The rest of the arguments are a list of 108 * calls to cmocka_unit_test or cmocka_unit_test_setup_teardown to run the 109 * individual unit tests. 110 */ 111 #define PCMK__UNIT_TEST(group_setup, group_teardown, ...) \ 112 int \ 113 main(int argc, char **argv) \ 114 { \ 115 const struct CMUnitTest t[] = { \ 116 __VA_ARGS__ \ 117 }; \ 118 cmocka_set_message_output(CM_OUTPUT_TAP); \ 119 return cmocka_run_group_tests(t, group_setup, group_teardown); \ 120 } 121 122 #endif /* CRM_COMMON_UNITTEST_INTERNAL__H */