Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F3686969
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
21 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/include/crm/common/unittest_internal.h b/include/crm/common/unittest_internal.h
index 3b6d9fd242..13378a8962 100644
--- a/include/crm/common/unittest_internal.h
+++ b/include/crm/common/unittest_internal.h
@@ -1,131 +1,142 @@
/*
* Copyright 2022-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cmocka.h>
#ifndef CRM_COMMON_UNITTEST_INTERNAL__H
#define CRM_COMMON_UNITTEST_INTERNAL__H
/* internal unit testing related utilities */
+#if (PCMK__WITH_COVERAGE == 1)
+/* This function isn't exposed anywhere. The following prototype was taken from
+ * /usr/lib/gcc/x86_64-redhat-linux/??/include/gcov.h
+ */
+extern void __gcov_dump(void);
+#else
+#define __gcov_dump()
+#endif
+
/*!
* \internal
* \brief Assert that a statement aborts through CRM_ASSERT().
*
* \param[in] stmt Statement to execute; can be an expression.
*
* A cmocka-like assert macro for use in unit testing. This one verifies that a
* statement aborts through CRM_ASSERT(), erroring out if that is not the case.
*
* This macro works by running the statement in a forked child process with core
* dumps disabled (CRM_ASSERT() calls \c abort(), which will write out a core
* dump). The parent waits for the child to exit and checks why. If the child
* received a \c SIGABRT, the test passes. For all other cases, the test fails.
*
* \note If cmocka's expect_*() or will_return() macros are called along with
* pcmk__assert_asserts(), they must be called within a block that is
* passed as the \c stmt argument. That way, the values are added only to
* the child's queue. Otherwise, values added to the parent's queue will
* never be popped, and the test will fail.
*/
#define pcmk__assert_asserts(stmt) \
do { \
pid_t p = fork(); \
if (p == 0) { \
struct rlimit cores = { 0, 0 }; \
setrlimit(RLIMIT_CORE, &cores); \
stmt; \
+ __gcov_dump(); \
_exit(0); \
} else if (p > 0) { \
int wstatus = 0; \
if (waitpid(p, &wstatus, 0) == -1) { \
fail_msg("waitpid failed"); \
} \
if (!(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGABRT)) { \
fail_msg("statement terminated in child without asserting"); \
} \
} else { \
fail_msg("unable to fork for assert test"); \
} \
} while (0);
/*!
* \internal
* \brief Assert that a statement aborts
*
* This is exactly the same as pcmk__assert_asserts (CRM_ASSERT() is implemented
* with abort()), but given a different name for clarity.
*/
#define pcmk__assert_aborts(stmt) pcmk__assert_asserts(stmt)
/*!
* \internal
* \brief Assert that a statement exits with the expected exit status.
*
* \param[in] stmt Statement to execute; can be an expression.
* \param[in] rc The expected exit status.
*
* This functions just like \c pcmk__assert_asserts, except that it tests for
* an expected exit status. Abnormal termination or incorrect exit status is
* treated as a failure of the test.
*
* In the event that stmt does not exit at all, the special code \c CRM_EX_NONE
* will be returned. It is expected that this code is not used anywhere, thus
* always causing an error.
*/
#define pcmk__assert_exits(rc, stmt) \
do { \
pid_t p = fork(); \
if (p == 0) { \
struct rlimit cores = { 0, 0 }; \
setrlimit(RLIMIT_CORE, &cores); \
stmt; \
+ __gcov_dump(); \
_exit(CRM_EX_NONE); \
} else if (p > 0) { \
int wstatus = 0; \
if (waitpid(p, &wstatus, 0) == -1) { \
fail_msg("waitpid failed"); \
} \
if (!WIFEXITED(wstatus)) { \
fail_msg("statement terminated abnormally"); \
} else if (WEXITSTATUS(wstatus) != rc) { \
fail_msg("statement exited with %d, not expected %d", WEXITSTATUS(wstatus), rc); \
} \
} else { \
fail_msg("unable to fork for assert test"); \
} \
} while (0);
/* Generate the main function of most unit test files. Typically, group_setup
* and group_teardown will be NULL. The rest of the arguments are a list of
* calls to cmocka_unit_test or cmocka_unit_test_setup_teardown to run the
* individual unit tests.
*/
#define PCMK__UNIT_TEST(group_setup, group_teardown, ...) \
int \
main(int argc, char **argv) \
{ \
const struct CMUnitTest t[] = { \
__VA_ARGS__ \
}; \
cmocka_set_message_output(CM_OUTPUT_TAP); \
return cmocka_run_group_tests(t, group_setup, group_teardown); \
}
#endif /* CRM_COMMON_UNITTEST_INTERNAL__H */
diff --git a/lib/common/mock.c b/lib/common/mock.c
index ba1e7c4414..f8be6f7842 100644
--- a/lib/common/mock.c
+++ b/lib/common/mock.c
@@ -1,476 +1,498 @@
/*
* Copyright 2021-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <errno.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <grp.h>
#include <cmocka.h>
+#include <crm/common/unittest_internal.h>
#include "mock_private.h"
/* This file is only used when running "make check". It is built into
* libcrmcommon_test.a, not into libcrmcommon.so. It is used to support
* constructing mock versions of library functions for unit testing.
*
* HOW TO ADD A MOCKED FUNCTION:
*
* - In this file, declare a bool pcmk__mock_X variable, and define a __wrap_X
* function with the same prototype as the actual function that performs the
* desired behavior if pcmk__mock_X is true and calls __real_X otherwise.
* You can use cmocka's mock_type() and mock_ptr_type() to pass extra
* information to the mocked function (see existing examples for details).
*
* - In mock_private.h, add declarations for extern bool pcmk__mock_X and the
* __real_X and __wrap_X function prototypes.
*
* - In mk/tap.mk, add the function name to the WRAPPED variable.
*
* HOW TO USE A MOCKED FUNCTION:
*
* - #include "mock_private.h" in your test file.
*
* - Write your test cases using pcmk__mock_X and cmocka's will_return() as
* needed per the comments for the mocked function below. See existing test
* cases for examples.
*/
// LCOV_EXCL_START
+
+/* abort()
+ *
+ * Always mock abort - there's no pcmk__mock_abort tuneable to control this.
+ * Because abort calls _exit(), which doesn't run any of the things registered
+ * with atexit(), coverage numbers do not get written out. This most noticably
+ * affects places where we are testing that things abort when they should.
+ *
+ * The solution is this wrapper that is always enabled when we are running
+ * unit tests (mock.c does not get included for the regular libcrmcommon.so).
+ * All it does is dump coverage data and call the real abort().
+ */
+_Noreturn void
+__wrap_abort(void)
+{
+#if (PCMK__WITH_COVERAGE == 1)
+ __gcov_dump();
+#endif
+ __real_abort();
+}
+
/* calloc()
*
* If pcmk__mock_calloc is set to true, later calls to calloc() will return
* NULL and must be preceded by:
*
* expect_*(__wrap_calloc, nmemb[, ...]);
* expect_*(__wrap_calloc, size[, ...]);
*
* expect_* functions: https://api.cmocka.org/group__cmocka__param.html
*/
bool pcmk__mock_calloc = false;
void *
__wrap_calloc(size_t nmemb, size_t size)
{
if (!pcmk__mock_calloc) {
return __real_calloc(nmemb, size);
}
check_expected(nmemb);
check_expected(size);
return NULL;
}
/* getenv()
*
* If pcmk__mock_getenv is set to true, later calls to getenv() must be preceded
* by:
*
* expect_*(__wrap_getenv, name[, ...]);
* will_return(__wrap_getenv, return_value);
*
* expect_* functions: https://api.cmocka.org/group__cmocka__param.html
*/
bool pcmk__mock_getenv = false;
char *
__wrap_getenv(const char *name)
{
if (!pcmk__mock_getenv) {
return __real_getenv(name);
}
check_expected_ptr(name);
return mock_ptr_type(char *);
}
/* realloc()
*
* If pcmk__mock_realloc is set to true, later calls to realloc() will return
* NULL and must be preceded by:
*
* expect_*(__wrap_realloc, ptr[, ...]);
* expect_*(__wrap_realloc, size[, ...]);
*
* expect_* functions: https://api.cmocka.org/group__cmocka__param.html
*/
bool pcmk__mock_realloc = false;
void *
__wrap_realloc(void *ptr, size_t size)
{
if (!pcmk__mock_realloc) {
return __real_realloc(ptr, size);
}
check_expected_ptr(ptr);
check_expected(size);
return NULL;
}
/* setenv()
*
* If pcmk__mock_setenv is set to true, later calls to setenv() must be preceded
* by:
*
* expect_*(__wrap_setenv, name[, ...]);
* expect_*(__wrap_setenv, value[, ...]);
* expect_*(__wrap_setenv, overwrite[, ...]);
* will_return(__wrap_setenv, errno_to_set);
*
* expect_* functions: https://api.cmocka.org/group__cmocka__param.html
*
* The mocked function will return 0 if errno_to_set is 0, and -1 otherwise.
*/
bool pcmk__mock_setenv = false;
int
__wrap_setenv(const char *name, const char *value, int overwrite)
{
if (!pcmk__mock_setenv) {
return __real_setenv(name, value, overwrite);
}
check_expected_ptr(name);
check_expected_ptr(value);
check_expected(overwrite);
errno = mock_type(int);
return (errno == 0)? 0 : -1;
}
/* unsetenv()
*
* If pcmk__mock_unsetenv is set to true, later calls to unsetenv() must be
* preceded by:
*
* expect_*(__wrap_unsetenv, name[, ...]);
* will_return(__wrap_setenv, errno_to_set);
*
* expect_* functions: https://api.cmocka.org/group__cmocka__param.html
*
* The mocked function will return 0 if errno_to_set is 0, and -1 otherwise.
*/
bool pcmk__mock_unsetenv = false;
int
__wrap_unsetenv(const char *name)
{
if (!pcmk__mock_unsetenv) {
return __real_unsetenv(name);
}
check_expected_ptr(name);
errno = mock_type(int);
return (errno == 0)? 0 : -1;
}
/* getpid()
*
* If pcmk__mock_getpid is set to true, later calls to getpid() must be preceded
* by:
*
* will_return(__wrap_getpid, return_value);
*/
bool pcmk__mock_getpid = false;
pid_t
__wrap_getpid(void)
{
return pcmk__mock_getpid? mock_type(pid_t) : __real_getpid();
}
/* setgrent(), getgrent() and endgrent()
*
* If pcmk__mock_grent is set to true, getgrent() will behave as if the only
* groups on the system are:
*
* - grp0 (user0, user1)
* - grp1 (user1)
* - grp2 (user2, user1)
*/
bool pcmk__mock_grent = false;
// Index of group that will be returned next from getgrent()
static int group_idx = 0;
// Data used for testing
static const char* grp0_members[] = {
"user0", "user1", NULL
};
static const char* grp1_members[] = {
"user1", NULL
};
static const char* grp2_members[] = {
"user2", "user1", NULL
};
/* An array of "groups" (a struct from grp.h)
*
* The members of the groups are initalized here to some testing data, casting
* away the consts to make the compiler happy and simplify initialization. We
* never actually change these variables during the test!
*
* string literal = const char* (cannot be changed b/c ? )
* vs. char* (it's getting casted to this)
*/
static const int NUM_GROUPS = 3;
static struct group groups[] = {
{(char*)"grp0", (char*)"", 0, (char**)grp0_members},
{(char*)"grp1", (char*)"", 1, (char**)grp1_members},
{(char*)"grp2", (char*)"", 2, (char**)grp2_members},
};
// This function resets the group_idx to 0.
void
__wrap_setgrent(void) {
if (pcmk__mock_grent) {
group_idx = 0;
} else {
__real_setgrent();
}
}
/* This function returns the next group entry in the list of groups, or
* NULL if there aren't any left.
* group_idx is a global variable which keeps track of where you are in the list
*/
struct group *
__wrap_getgrent(void) {
if (pcmk__mock_grent) {
if (group_idx >= NUM_GROUPS) {
return NULL;
}
return &groups[group_idx++];
} else {
return __real_getgrent();
}
}
void
__wrap_endgrent(void) {
if (!pcmk__mock_grent) {
__real_endgrent();
}
}
/* fopen()
*
* If pcmk__mock_fopen is set to true, later calls to fopen() must be
* preceded by:
*
* expect_*(__wrap_fopen, pathname[, ...]);
* expect_*(__wrap_fopen, mode[, ...]);
* will_return(__wrap_fopen, errno_to_set);
*
* expect_* functions: https://api.cmocka.org/group__cmocka__param.html
*
* This has two mocked functions, since fopen() is sometimes actually fopen64().
*/
bool pcmk__mock_fopen = false;
FILE *
__wrap_fopen(const char *pathname, const char *mode)
{
if (pcmk__mock_fopen) {
check_expected_ptr(pathname);
check_expected_ptr(mode);
errno = mock_type(int);
if (errno != 0) {
return NULL;
} else {
return __real_fopen(pathname, mode);
}
} else {
return __real_fopen(pathname, mode);
}
}
#ifdef HAVE_FOPEN64
FILE *
__wrap_fopen64(const char *pathname, const char *mode)
{
if (pcmk__mock_fopen) {
check_expected_ptr(pathname);
check_expected_ptr(mode);
errno = mock_type(int);
if (errno != 0) {
return NULL;
} else {
return __real_fopen64(pathname, mode);
}
} else {
return __real_fopen64(pathname, mode);
}
}
#endif
/* getpwnam_r()
*
* If pcmk__mock_getpwnam_r is set to true, later calls to getpwnam_r() must be
* preceded by:
*
* expect_*(__wrap_getpwnam_r, name[, ...]);
* expect_*(__wrap_getpwnam_r, pwd[, ...]);
* expect_*(__wrap_getpwnam_r, buf[, ...]);
* expect_*(__wrap_getpwnam_r, buflen[, ...]);
* expect_*(__wrap_getpwnam_r, result[, ...]);
* will_return(__wrap_getpwnam_r, return_value);
* will_return(__wrap_getpwnam_r, ptr_to_result_struct);
*
* expect_* functions: https://api.cmocka.org/group__cmocka__param.html
*/
bool pcmk__mock_getpwnam_r = false;
int
__wrap_getpwnam_r(const char *name, struct passwd *pwd, char *buf,
size_t buflen, struct passwd **result)
{
if (pcmk__mock_getpwnam_r) {
int retval = mock_type(int);
check_expected_ptr(name);
check_expected_ptr(pwd);
check_expected_ptr(buf);
check_expected(buflen);
check_expected_ptr(result);
*result = mock_ptr_type(struct passwd *);
return retval;
} else {
return __real_getpwnam_r(name, pwd, buf, buflen, result);
}
}
/*
* If pcmk__mock_readlink is set to true, later calls to readlink() must be
* preceded by:
*
* expect_*(__wrap_readlink, path[, ...]);
* expect_*(__wrap_readlink, buf[, ...]);
* expect_*(__wrap_readlink, bufsize[, ...]);
* will_return(__wrap_readlink, errno_to_set);
* will_return(__wrap_readlink, link_contents);
*
* expect_* functions: https://api.cmocka.org/group__cmocka__param.html
*
* The mocked function will return 0 if errno_to_set is 0, and -1 otherwise.
*/
bool pcmk__mock_readlink = false;
ssize_t
__wrap_readlink(const char *restrict path, char *restrict buf,
size_t bufsize)
{
if (pcmk__mock_readlink) {
const char *contents = NULL;
check_expected_ptr(path);
check_expected_ptr(buf);
check_expected(bufsize);
errno = mock_type(int);
contents = mock_ptr_type(const char *);
if (errno == 0) {
strncpy(buf, contents, bufsize - 1);
return strlen(contents);
}
return -1;
} else {
return __real_readlink(path, buf, bufsize);
}
}
/* strdup()
*
* If pcmk__mock_strdup is set to true, later calls to strdup() will return
* NULL and must be preceded by:
*
* expect_*(__wrap_strdup, s[, ...]);
*
* expect_* functions: https://api.cmocka.org/group__cmocka__param.html
*/
bool pcmk__mock_strdup = false;
char *
__wrap_strdup(const char *s)
{
if (!pcmk__mock_strdup) {
return __real_strdup(s);
}
check_expected_ptr(s);
return NULL;
}
/* uname()
*
* If pcmk__mock_uname is set to true, later calls to uname() must be preceded
* by:
*
* expect_*(__wrap_uname, buf[, ...]);
* will_return(__wrap_uname, return_value);
* will_return(__wrap_uname, node_name_for_buf_parameter_to_uname);
*
* expect_* functions: https://api.cmocka.org/group__cmocka__param.html
*/
bool pcmk__mock_uname = false;
int
__wrap_uname(struct utsname *buf)
{
if (pcmk__mock_uname) {
int retval = 0;
char *result = NULL;
check_expected_ptr(buf);
retval = mock_type(int);
result = mock_ptr_type(char *);
if (result != NULL) {
strcpy(buf->nodename, result);
}
return retval;
} else {
return __real_uname(buf);
}
}
// LCOV_EXCL_STOP
diff --git a/lib/common/mock_private.h b/lib/common/mock_private.h
index 61b2bb4850..8d30ba56b7 100644
--- a/lib/common/mock_private.h
+++ b/lib/common/mock_private.h
@@ -1,85 +1,88 @@
/*
* Copyright 2021-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef MOCK_PRIVATE__H
# define MOCK_PRIVATE__H
#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <grp.h>
/* This header is for the sole use of libcrmcommon_test and unit tests */
+_Noreturn void __real_abort(void);
+_Noreturn void __wrap_abort(void);
+
extern bool pcmk__mock_calloc;
void *__real_calloc(size_t nmemb, size_t size);
void *__wrap_calloc(size_t nmemb, size_t size);
extern bool pcmk__mock_fopen;
FILE *__real_fopen(const char *pathname, const char *mode);
FILE *__wrap_fopen(const char *pathname, const char *mode);
#ifdef HAVE_FOPEN64
FILE *__real_fopen64(const char *pathname, const char *mode);
FILE *__wrap_fopen64(const char *pathname, const char *mode);
#endif
extern bool pcmk__mock_getenv;
char *__real_getenv(const char *name);
char *__wrap_getenv(const char *name);
extern bool pcmk__mock_realloc;
void *__real_realloc(void *ptr, size_t size);
void *__wrap_realloc(void *ptr, size_t size);
extern bool pcmk__mock_setenv;
int __real_setenv(const char *name, const char *value, int overwrite);
int __wrap_setenv(const char *name, const char *value, int overwrite);
extern bool pcmk__mock_unsetenv;
int __real_unsetenv(const char *name);
int __wrap_unsetenv(const char *name);
extern bool pcmk__mock_getpid;
pid_t __real_getpid(void);
pid_t __wrap_getpid(void);
extern bool pcmk__mock_grent;
void __real_setgrent(void);
void __wrap_setgrent(void);
struct group * __wrap_getgrent(void);
struct group * __real_getgrent(void);
void __wrap_endgrent(void);
void __real_endgrent(void);
extern bool pcmk__mock_getpwnam_r;
int __real_getpwnam_r(const char *name, struct passwd *pwd,
char *buf, size_t buflen, struct passwd **result);
int __wrap_getpwnam_r(const char *name, struct passwd *pwd,
char *buf, size_t buflen, struct passwd **result);
extern bool pcmk__mock_readlink;
ssize_t __real_readlink(const char *restrict path, char *restrict buf,
size_t bufsize);
ssize_t __wrap_readlink(const char *restrict path, char *restrict buf,
size_t bufsize);
extern bool pcmk__mock_strdup;
char *__real_strdup(const char *s);
char *__wrap_strdup(const char *s);
extern bool pcmk__mock_uname;
int __real_uname(struct utsname *buf);
int __wrap_uname(struct utsname *buf);
#endif // MOCK_PRIVATE__H
diff --git a/mk/tap.mk b/mk/tap.mk
index 14a1cf1059..950b72cd65 100644
--- a/mk/tap.mk
+++ b/mk/tap.mk
@@ -1,37 +1,38 @@
#
# Copyright 2021-2024 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
AM_TESTS_ENVIRONMENT= \
G_DEBUG=gc-friendly \
MALLOC_CHECK_=2 \
MALLOC_PERTURB_=$$(($${RANDOM:-256} % 256))
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tests/tap-driver.sh
LOG_COMPILER = $(top_srcdir)/tests/tap-test
CLEANFILES = *.log *.trs
-WRAPPED = calloc \
+WRAPPED = abort \
+ calloc \
endgrent \
fopen \
getenv \
getpid \
getgrent \
getpwnam_r \
readlink \
realloc \
setenv \
setgrent \
strdup \
uname \
unsetenv
if WRAPPABLE_FOPEN64
WRAPPED += fopen64
endif
LDFLAGS_WRAP = $(foreach fn,$(WRAPPED),-Wl,--wrap=$(fn))
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Apr 21, 6:15 PM (1 d, 54 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1665163
Default Alt Text
(21 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment