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