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