1 /* 2 * Copyright 2022-2025 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 #ifndef PCMK__CRM_COMMON_UNITTEST_INTERNAL__H 11 #define PCMK__CRM_COMMON_UNITTEST_INTERNAL__H 12 13 #include <signal.h> 14 #include <stdarg.h> 15 #include <stdint.h> 16 #include <setjmp.h> 17 #include <sys/resource.h> 18 #include <sys/types.h> 19 #include <sys/wait.h> 20 #include <unistd.h> 21 22 #include <cmocka.h> 23 24 #include <crm/common/xml.h> 25 26 #ifdef __cplusplus 27 extern "C" { 28 #endif 29 30 /* internal unit testing related utilities */ 31 32 #if (PCMK__WITH_COVERAGE == 1) 33 /* This function isn't exposed anywhere. The following prototype was taken from 34 * /usr/lib/gcc/x86_64-redhat-linux/??/include/gcov.h 35 */ 36 extern void __gcov_dump(void); 37 #else 38 #define __gcov_dump() 39 #endif 40 41 /*! 42 * \internal 43 * \brief Assert that the XML output from an API function is valid 44 * 45 * \param[in] xml The XML output of some public pacemaker API function 46 * 47 * Run the given XML through xmllint and attempt to validate it against the 48 * api-result.rng schema file. Assert if validation fails. 49 * 50 * \note PCMK_schema_directory needs to be set to the directory containing 51 * the built schema files before calling this function. Typically, 52 * this will be done in Makefile.am. 53 */ 54 void pcmk__assert_validates(xmlNode *xml); 55 56 /*! 57 * \internal 58 * \brief Perform setup for a group of unit tests that will manipulate XML 59 * 60 * This function is suitable for being passed as the first argument to the 61 * \c PCMK__UNIT_TEST macro. 62 * 63 * \param[in] state The cmocka state object, currently unused by this 64 * function 65 */ 66 int pcmk__xml_test_setup_group(void **state); 67 68 int pcmk__xml_test_teardown_group(void **state); 69 70 /*! 71 * \internal 72 * \brief Copy the given CIB file to a temporary file so it can be modified 73 * as part of doing unit tests, returning the full temporary file or 74 * \c NULL on error. 75 * 76 * This function should be called as part of the process of setting up any 77 * single unit test that would access and modify a CIB. That is, it should 78 * be called from whatever function is the second argument to 79 * cmocka_unit_test_setup_teardown. 80 * 81 * \param[in] in_file The filename of the input CIB file, which must 82 * exist in the \c $PCMK_CTS_CLI_DIR directory. This 83 * should only be the filename, not the complete 84 * path. 85 */ 86 char *pcmk__cib_test_copy_cib(const char *in_file); 87 88 /*! 89 * \internal 90 * \brief Clean up whatever was done by a previous call to 91 * \c pcmk__cib_test_copy_cib. 92 * 93 * This function should be called as part of the process of tearing down 94 * any single unit test that accessed a CIB. That is, it should be called 95 * from whatever function is the third argument to 96 * \c cmocka_unit_test_setup_teardown. 97 * 98 * \param[in] out_path The complete path to the temporary CIB location. 99 * This is the return value of 100 * \c pcmk__cib_test_copy_cib. 101 */ 102 void pcmk__cib_test_cleanup(char *out_path); 103 104 void pcmk__test_init_logging(const char *name, const char *filename); 105 106 /*! 107 * \internal 108 * \brief Assert that a statement aborts through pcmk__assert(). 109 * 110 * \param[in] stmt Statement to execute; can be an expression. 111 * 112 * A cmocka-like assert macro for use in unit testing. This one verifies that a 113 * statement aborts through pcmk__assert(), erroring out if that is not the 114 * case. 115 * 116 * This macro works by running the statement in a forked child process with core 117 * dumps disabled (pcmk__assert() calls \c abort(), which will write out a core 118 * dump). The parent waits for the child to exit and checks why. If the child 119 * received a \c SIGABRT, the test passes. For all other cases, the test fails. 120 * 121 * \note If cmocka's expect_*() or will_return() macros are called along with 122 * pcmk__assert_asserts(), they must be called within a block that is 123 * passed as the \c stmt argument. That way, the values are added only to 124 * the child's queue. Otherwise, values added to the parent's queue will 125 * never be popped, and the test will fail. 126 */ 127 #define pcmk__assert_asserts(stmt) \ 128 do { \ 129 pid_t p = fork(); \ 130 if (p == 0) { \ 131 struct rlimit cores = { 0, 0 }; \ 132 setrlimit(RLIMIT_CORE, &cores); \ 133 stmt; \ 134 __gcov_dump(); \ 135 _exit(0); \ 136 } else if (p > 0) { \ 137 int wstatus = 0; \ 138 if (waitpid(p, &wstatus, 0) == -1) { \ 139 fail_msg("waitpid failed"); \ 140 } \ 141 if (!(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGABRT)) { \ 142 fail_msg("statement terminated in child without asserting"); \ 143 } \ 144 } else { \ 145 fail_msg("unable to fork for assert test"); \ 146 } \ 147 } while (0); 148 149 /*! 150 * \internal 151 * \brief Assert that a statement aborts 152 * 153 * This is exactly the same as pcmk__assert_asserts (pcmk__assert() is 154 * implemented with abort()), but given a different name for clarity. 155 */ 156 #define pcmk__assert_aborts(stmt) pcmk__assert_asserts(stmt) 157 158 /*! 159 * \internal 160 * \brief Assert that a statement exits with the expected exit status. 161 * 162 * \param[in] stmt Statement to execute; can be an expression. 163 * \param[in] rc The expected exit status. 164 * 165 * This functions just like \c pcmk__assert_asserts, except that it tests for 166 * an expected exit status. Abnormal termination or incorrect exit status is 167 * treated as a failure of the test. 168 * 169 * In the event that stmt does not exit at all, the special code \c CRM_EX_NONE 170 * will be returned. It is expected that this code is not used anywhere, thus 171 * always causing an error. 172 */ 173 #define pcmk__assert_exits(rc, stmt) \ 174 do { \ 175 pid_t p = fork(); \ 176 if (p == 0) { \ 177 struct rlimit cores = { 0, 0 }; \ 178 setrlimit(RLIMIT_CORE, &cores); \ 179 stmt; \ 180 __gcov_dump(); \ 181 _exit(CRM_EX_NONE); \ 182 } else if (p > 0) { \ 183 int wstatus = 0; \ 184 if (waitpid(p, &wstatus, 0) == -1) { \ 185 fail_msg("waitpid failed"); \ 186 } \ 187 if (!WIFEXITED(wstatus)) { \ 188 fail_msg("statement terminated abnormally"); \ 189 } else if (WEXITSTATUS(wstatus) != rc) { \ 190 fail_msg("statement exited with %d, not expected %d", WEXITSTATUS(wstatus), rc); \ 191 } \ 192 } else { \ 193 fail_msg("unable to fork for assert test"); \ 194 } \ 195 } while (0); 196 197 /* Generate the main function of most unit test files. Typically, group_setup 198 * and group_teardown will be NULL. The rest of the arguments are a list of 199 * calls to cmocka_unit_test or cmocka_unit_test_setup_teardown to run the 200 * individual unit tests. 201 */ 202 #define PCMK__UNIT_TEST(group_setup, group_teardown, ...) \ 203 int \ 204 main(int argc, char **argv) \ 205 { \ 206 const struct CMUnitTest t[] = { \ 207 __VA_ARGS__ \ 208 }; \ 209 cmocka_set_message_output(CM_OUTPUT_TAP); \ 210 return cmocka_run_group_tests(t, group_setup, group_teardown); \ 211 } 212 213 #ifdef __cplusplus 214 } 215 #endif 216 217 #endif // PCMK__CRM_COMMON_UNITTEST_INTERNAL__H