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