diff --git a/lib/common/mock.c b/lib/common/mock.c index 6f837adc0d..ba1e7c4414 100644 --- a/lib/common/mock.c +++ b/lib/common/mock.c @@ -1,451 +1,476 @@ /* - * Copyright 2021-2023 the Pacemaker project contributors + * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 /* 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 b0e0ed2769..61b2bb4850 100644 --- a/lib/common/mock_private.h +++ b/lib/common/mock_private.h @@ -1,81 +1,85 @@ /* - * Copyright 2021-2023 the Pacemaker project contributors + * 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 #include #include #include #include #include #include #include #include /* This header is for the sole use of libcrmcommon_test and unit tests */ 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 fd6d4e2dfd..14a1cf1059 100644 --- a/mk/tap.mk +++ b/mk/tap.mk @@ -1,36 +1,37 @@ # -# Copyright 2021-2023 the Pacemaker project contributors +# 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 \ 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))