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 */