diff --git a/include/crm/common/unittest_internal.h b/include/crm/common/unittest_internal.h
index 096b2072d3..4070539530 100644
--- a/include/crm/common/unittest_internal.h
+++ b/include/crm/common/unittest_internal.h
@@ -1,56 +1,68 @@
 /*
  * Copyright 2022 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 */
 
-/* A cmocka-like assert macro for use in unit testing.  This one verifies that
- * an expression aborts through CRM_ASSERT, erroring out if that is not the case.
+/*!
+ * \internal
+ * \brief Assert that a statement aborts through CRM_ASSERT().
  *
- * This macro works by running the expression in a forked child process with core
- * dumps disabled (CRM_ASSERT calls abort(), which will write out a core dump).
- * The parent waits for the child to exit and checks why.  If the child received
- * a SIGABRT, the test passes.  For all other cases, the test fails.
+ * \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(expr) \
+#define pcmk__assert_asserts(stmt) \
     do { \
         pid_t p = fork(); \
         if (p == 0) { \
             struct rlimit cores = { 0, 0 }; \
             setrlimit(RLIMIT_CORE, &cores); \
-            expr; \
+            stmt; \
             _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("expr terminated without asserting"); \
+                fail_msg("statement terminated in child without asserting"); \
             } \
         } else { \
             fail_msg("unable to fork for assert test"); \
         } \
     } while (0);
 
 #endif /* CRM_COMMON_UNITTEST_INTERNAL__H */
diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h
index b11ff2b4c9..3989751607 100644
--- a/lib/common/crmcommon_private.h
+++ b/lib/common/crmcommon_private.h
@@ -1,294 +1,300 @@
 /*
  * Copyright 2018-2022 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 CRMCOMMON_PRIVATE__H
 #  define CRMCOMMON_PRIVATE__H
 
 /* This header is for the sole use of libcrmcommon, so that functions can be
  * declared with G_GNUC_INTERNAL for efficiency.
  */
 
 #include <stdint.h>         // uint8_t, uint32_t
 #include <stdbool.h>        // bool
 #include <sys/types.h>      // size_t
 #include <glib.h>           // GList
 #include <libxml/tree.h>    // xmlNode, xmlAttr
 #include <qb/qbipcc.h>      // struct qb_ipc_response_header
 
 // Decent chunk size for processing large amounts of data
 #define PCMK__BUFFER_SIZE 4096
 
 #if defined(PCMK__UNIT_TESTING)
 #undef G_GNUC_INTERNAL
 #define G_GNUC_INTERNAL
 #endif
 
 /* When deleting portions of an XML tree, we keep a record so we can know later
  * (e.g. when checking differences) that something was deleted.
  */
 typedef struct pcmk__deleted_xml_s {
         char *path;
         int position;
 } pcmk__deleted_xml_t;
 
 typedef struct xml_private_s {
         long check;
         uint32_t flags;
         char *user;
         GList *acls;
         GList *deleted_objs; // List of pcmk__deleted_xml_t
 } xml_private_t;
 
 #define pcmk__set_xml_flags(xml_priv, flags_to_set) do {                    \
         (xml_priv)->flags = pcmk__set_flags_as(__func__, __LINE__,          \
             LOG_NEVER, "XML", "XML node", (xml_priv)->flags,                \
             (flags_to_set), #flags_to_set);                                 \
     } while (0)
 
 #define pcmk__clear_xml_flags(xml_priv, flags_to_clear) do {                \
         (xml_priv)->flags = pcmk__clear_flags_as(__func__, __LINE__,        \
             LOG_NEVER, "XML", "XML node", (xml_priv)->flags,                \
             (flags_to_clear), #flags_to_clear);                             \
     } while (0)
 
 G_GNUC_INTERNAL
 void pcmk__xml2text(xmlNode *data, int options, char **buffer, int *offset,
                     int *max, int depth);
 
 G_GNUC_INTERNAL
 void pcmk__buffer_add_char(char **buffer, int *offset, int *max, char c);
 
 G_GNUC_INTERNAL
 bool pcmk__tracking_xml_changes(xmlNode *xml, bool lazy);
 
 G_GNUC_INTERNAL
 int pcmk__element_xpath(const char *prefix, xmlNode *xml, char *buffer,
                         int offset, size_t buffer_size);
 
 G_GNUC_INTERNAL
 void pcmk__mark_xml_created(xmlNode *xml);
 
 G_GNUC_INTERNAL
 int pcmk__xml_position(xmlNode *xml, enum xml_private_flags ignore_if_set);
 
 G_GNUC_INTERNAL
 xmlNode *pcmk__xml_match(xmlNode *haystack, xmlNode *needle, bool exact);
 
 G_GNUC_INTERNAL
 void pcmk__xe_log(int log_level, const char *file, const char *function,
                   int line, const char *prefix, xmlNode *data, int depth,
                   int options);
 
 G_GNUC_INTERNAL
 void pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
                       bool as_diff);
 
 G_GNUC_INTERNAL
 xmlNode *pcmk__xc_match(xmlNode *root, xmlNode *search_comment, bool exact);
 
 G_GNUC_INTERNAL
 void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update);
 
 G_GNUC_INTERNAL
 void pcmk__free_acls(GList *acls);
 
 G_GNUC_INTERNAL
 void pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user);
 
 G_GNUC_INTERNAL
 bool pcmk__is_user_in_group(const char *user, const char *group);
 
 G_GNUC_INTERNAL
 void pcmk__apply_acl(xmlNode *xml);
 
 G_GNUC_INTERNAL
 void pcmk__apply_creation_acl(xmlNode *xml, bool check_top);
 
 G_GNUC_INTERNAL
 void pcmk__mark_xml_attr_dirty(xmlAttr *a);
 
 G_GNUC_INTERNAL
 bool pcmk__xa_filterable(const char *name);
 
 static inline const char *
 pcmk__xml_attr_value(const xmlAttr *attr)
 {
     return ((attr == NULL) || (attr->children == NULL))? NULL
            : (const char *) attr->children->content;
 }
 
 /*
  * IPC
  */
 
 #define PCMK__IPC_VERSION 1
 
 #define PCMK__CONTROLD_API_MAJOR "1"
 #define PCMK__CONTROLD_API_MINOR "0"
 
 // IPC behavior that varies by daemon
 typedef struct pcmk__ipc_methods_s {
     /*!
      * \internal
      * \brief Allocate any private data needed by daemon IPC
      *
      * \param[in] api  IPC API connection
      *
      * \return Standard Pacemaker return code
      */
     int (*new_data)(pcmk_ipc_api_t *api);
 
     /*!
      * \internal
      * \brief Free any private data used by daemon IPC
      *
      * \param[in] api_data  Data allocated by new_data() method
      */
     void (*free_data)(void *api_data);
 
     /*!
      * \internal
      * \brief Perform daemon-specific handling after successful connection
      *
      * Some daemons require clients to register before sending any other
      * commands. The controller requires a CRM_OP_HELLO (with no reply), and
      * the CIB manager, executor, and fencer require a CRM_OP_REGISTER (with a
      * reply). Ideally this would be consistent across all daemons, but for now
      * this allows each to do its own authorization.
      *
      * \param[in] api  IPC API connection
      *
      * \return Standard Pacemaker return code
      */
     int (*post_connect)(pcmk_ipc_api_t *api);
 
     /*!
      * \internal
      * \brief Check whether an IPC request results in a reply
      *
      * \param[in] api      IPC API connection
      * \param[in] request  IPC request XML
      *
      * \return true if request would result in an IPC reply, false otherwise
      */
     bool (*reply_expected)(pcmk_ipc_api_t *api, xmlNode *request);
 
     /*!
      * \internal
      * \brief Perform daemon-specific handling of an IPC message
      *
      * \param[in] api  IPC API connection
      * \param[in] msg  Message read from IPC connection
      *
      * \return true if more IPC reply messages should be expected
      */
     bool (*dispatch)(pcmk_ipc_api_t *api, xmlNode *msg);
 
     /*!
      * \internal
      * \brief Perform daemon-specific handling of an IPC disconnect
      *
      * \param[in] api  IPC API connection
      */
     void (*post_disconnect)(pcmk_ipc_api_t *api);
 } pcmk__ipc_methods_t;
 
 // Implementation of pcmk_ipc_api_t
 struct pcmk_ipc_api_s {
     enum pcmk_ipc_server server;          // Daemon this IPC API instance is for
     enum pcmk_ipc_dispatch dispatch_type; // How replies should be dispatched
     size_t ipc_size_max;                  // maximum IPC buffer size
     crm_ipc_t *ipc;                       // IPC connection
     mainloop_io_t *mainloop_io;     // If using mainloop, I/O source for IPC
     bool free_on_disconnect;        // Whether disconnect should free object
     pcmk_ipc_callback_t cb;         // Caller-registered callback (if any)
     void *user_data;                // Caller-registered data (if any)
     void *api_data;                 // For daemon-specific use
     pcmk__ipc_methods_t *cmds;      // Behavior that varies by daemon
 };
 
 typedef struct pcmk__ipc_header_s {
     struct qb_ipc_response_header qb;
     uint32_t size_uncompressed;
     uint32_t size_compressed;
     uint32_t flags;
     uint8_t version;
 } pcmk__ipc_header_t;
 
 G_GNUC_INTERNAL
 int pcmk__send_ipc_request(pcmk_ipc_api_t *api, xmlNode *request);
 
 G_GNUC_INTERNAL
 void pcmk__call_ipc_callback(pcmk_ipc_api_t *api,
                              enum pcmk_ipc_event event_type,
                              crm_exit_t status, void *event_data);
 
 G_GNUC_INTERNAL
 unsigned int pcmk__ipc_buffer_size(unsigned int max);
 
 G_GNUC_INTERNAL
 bool pcmk__valid_ipc_header(const pcmk__ipc_header_t *header);
 
 G_GNUC_INTERNAL
 pcmk__ipc_methods_t *pcmk__attrd_api_methods(void);
 
 G_GNUC_INTERNAL
 pcmk__ipc_methods_t *pcmk__controld_api_methods(void);
 
 G_GNUC_INTERNAL
 pcmk__ipc_methods_t *pcmk__pacemakerd_api_methods(void);
 
 G_GNUC_INTERNAL
 pcmk__ipc_methods_t *pcmk__schedulerd_api_methods(void);
 
 
 /*
  * Logging
  */
 
 /*!
  * \brief Check the authenticity of the IPC socket peer process
  *
  * If everything goes well, peer's authenticity is verified by the means
  * of comparing against provided referential UID and GID (either satisfies),
  * and the result of this check can be deduced from the return value.
  * As an exception, detected UID of 0 ("root") satisfies arbitrary
  * provided referential daemon's credentials.
  *
  * \param[in]  qb_ipc  libqb client connection if available
  * \param[in]  sock    IPC related, connected Unix socket to check peer of
  * \param[in]  refuid  referential UID to check against
  * \param[in]  refgid  referential GID to check against
  * \param[out] gotpid  to optionally store obtained PID of the peer
  *                     (not available on FreeBSD, special value of 1
  *                     used instead, and the caller is required to
  *                     special case this value respectively)
  * \param[out] gotuid  to optionally store obtained UID of the peer
  * \param[out] gotgid  to optionally store obtained GID of the peer
  *
  * \return Standard Pacemaker return code
  *         ie: 0 if it the connection is authentic
  *         pcmk_rc_ipc_unauthorized if the connection is not authentic,
  *         standard errors.
  *
  * \note While this function is tolerant on what constitutes authorized
  *       IPC daemon process (its effective user matches UID=0 or \p refuid,
  *       or at least its group matches \p refgid), either or both (in case
  *       of UID=0) mismatches on the expected credentials of such peer
  *       process \e shall be investigated at the caller when value of 1
  *       gets returned there, since higher-than-expected privileges in
  *       respect to the expected/intended credentials possibly violate
  *       the least privilege principle and may pose an additional risk
  *       (i.e. such accidental inconsistency shall be eventually fixed).
  */
 int pcmk__crm_ipc_is_authentic_process(qb_ipcc_connection_t *qb_ipc, int sock, uid_t refuid, gid_t refgid,
                                        pid_t *gotpid, uid_t *gotuid, gid_t *gotgid);
 
 
+/*
+ * Utils
+ */
+#define PCMK__PW_BUFFER_LEN 500
+
+
 #endif  // CRMCOMMON_PRIVATE__H
diff --git a/lib/common/mock.c b/lib/common/mock.c
index 4b355d1cbc..f628b03942 100644
--- a/lib/common/mock.c
+++ b/lib/common/mock.c
@@ -1,314 +1,371 @@
 /*
  * Copyright 2021-2022 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 <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 "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 malloc() will return
- * NULL.
+ * 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)
 {
-    return pcmk__mock_calloc? NULL : __real_calloc(nmemb, 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)
 {
-    return pcmk__mock_getenv? mock_ptr_type(char *) : __real_getenv(name);
+    if (!pcmk__mock_getenv) {
+        return __real_getenv(name);
+    }
+    check_expected_ptr(name);
+    return mock_ptr_type(char *);
 }
 
 
 /* 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
  */
 
 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);
     }
 }
 
 
 /* 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.
+ * 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)
 {
-    return pcmk__mock_strdup? NULL : __real_strdup(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 = mock_type(int);
-        char *result = mock_ptr_type(char *);
+        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/tests/io/pcmk__full_path_test.c b/lib/common/tests/io/pcmk__full_path_test.c
index 7c8bddade6..2ae9cc3b3f 100644
--- a/lib/common/tests/io/pcmk__full_path_test.c
+++ b/lib/common/tests/io/pcmk__full_path_test.c
@@ -1,55 +1,60 @@
 /*
  * Copyright 2020-2022 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 <crm/common/unittest_internal.h>
 
 #include "mock_private.h"
 
 static void
 function_asserts(void **state)
 {
     pcmk__assert_asserts(pcmk__full_path(NULL, "/dir"));
     pcmk__assert_asserts(pcmk__full_path("file", NULL));
 
-    pcmk__mock_strdup = true;       // strdup() will return NULL
-    pcmk__assert_asserts(pcmk__full_path("/full/path", "/dir"));
-    pcmk__mock_strdup = false;      // Use real strdup()
+    pcmk__assert_asserts(
+        {
+            pcmk__mock_strdup = true;   // strdup() will return NULL
+            expect_string(__wrap_strdup, s, "/full/path");
+            pcmk__full_path("/full/path", "/dir");
+            pcmk__mock_strdup = false;  // Use real strdup()
+        }
+    );
 }
 
 static void
 full_path(void **state)
 {
     char *path = NULL;
 
     path = pcmk__full_path("file", "/dir");
     assert_int_equal(strcmp(path, "/dir/file"), 0);
     free(path);
 
     path = pcmk__full_path("/full/path", "/dir");
     assert_int_equal(strcmp(path, "/full/path"), 0);
     free(path);
 
     path = pcmk__full_path("../relative/path", "/dir");
     assert_int_equal(strcmp(path, "/dir/../relative/path"), 0);
     free(path);
 }
 
 int
 main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(function_asserts),
         cmocka_unit_test(full_path),
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/tests/io/pcmk__get_tmpdir_test.c b/lib/common/tests/io/pcmk__get_tmpdir_test.c
index eac8fc0f82..71941b0ce5 100644
--- a/lib/common/tests/io/pcmk__get_tmpdir_test.c
+++ b/lib/common/tests/io/pcmk__get_tmpdir_test.c
@@ -1,70 +1,76 @@
 /*
  * Copyright 2021-2022 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 <crm/common/unittest_internal.h>
 
 #include "mock_private.h"
 
 static void
 getenv_returns_invalid(void **state)
 {
     const char *result;
 
     pcmk__mock_getenv = true;
 
+    expect_string(__wrap_getenv, name, "TMPDIR");
     will_return(__wrap_getenv, NULL);                   // getenv("TMPDIR") return value
     result = pcmk__get_tmpdir();
     assert_string_equal(result, "/tmp");
 
+    expect_string(__wrap_getenv, name, "TMPDIR");
     will_return(__wrap_getenv, "");                     // getenv("TMPDIR") return value
     result = pcmk__get_tmpdir();
     assert_string_equal(result, "/tmp");
 
+    expect_string(__wrap_getenv, name, "TMPDIR");
     will_return(__wrap_getenv, "subpath");              // getenv("TMPDIR") return value
     result = pcmk__get_tmpdir();
     assert_string_equal(result, "/tmp");
 
     pcmk__mock_getenv = false;
 }
 
 static void
 getenv_returns_valid(void **state)
 {
     const char *result;
 
     pcmk__mock_getenv = true;
 
+    expect_string(__wrap_getenv, name, "TMPDIR");
     will_return(__wrap_getenv, "/var/tmp");             // getenv("TMPDIR") return value
     result = pcmk__get_tmpdir();
     assert_string_equal(result, "/var/tmp");
 
+    expect_string(__wrap_getenv, name, "TMPDIR");
     will_return(__wrap_getenv, "/");                    // getenv("TMPDIR") return value
     result = pcmk__get_tmpdir();
     assert_string_equal(result, "/");
 
+    expect_string(__wrap_getenv, name, "TMPDIR");
     will_return(__wrap_getenv, "/tmp/abcd.1234");       // getenv("TMPDIR") return value
     result = pcmk__get_tmpdir();
     assert_string_equal(result, "/tmp/abcd.1234");
 
     pcmk__mock_getenv = false;
 }
 
 int
 main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(getenv_returns_invalid),
         cmocka_unit_test(getenv_returns_valid),
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/tests/output/pcmk__output_new_test.c b/lib/common/tests/output/pcmk__output_new_test.c
index 14410830d6..4d27436ff6 100644
--- a/lib/common/tests/output/pcmk__output_new_test.c
+++ b/lib/common/tests/output/pcmk__output_new_test.c
@@ -1,152 +1,156 @@
 /*
  * Copyright 2022 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 <crm/common/unittest_internal.h>
 #include <crm/common/output_internal.h>
 
 #include "mock_private.h"
 
 static bool init_succeeds = true;
 
 static bool
 fake_text_init(pcmk__output_t *out) {
     return init_succeeds;
 }
 
 static void
 fake_text_free_priv(pcmk__output_t *out) {
     /* This function intentionally left blank */
 }
 
 /* "text" is the default for pcmk__output_new. */
 static pcmk__output_t *
 mk_fake_text_output(char **argv) {
     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 
     if (retval == NULL) {
         return NULL;
     }
 
     retval->fmt_name = "text";
     retval->init = fake_text_init;
     retval->free_priv = fake_text_free_priv;
 
     retval->register_message = pcmk__register_message;
     retval->message = pcmk__call_message;
 
     return retval;
 }
 
 static int
 setup(void **state) {
     pcmk__register_format(NULL, "text", mk_fake_text_output, NULL);
     return 0;
 }
 
 static int
 teardown(void **state) {
     pcmk__unregister_formats();
     return 0;
 }
 
 static void
 empty_formatters(void **state) {
     pcmk__output_t *out = NULL;
 
     pcmk__assert_asserts(pcmk__output_new(&out, "fake", NULL, NULL));
 }
 
 static void
 invalid_params(void **state) {
     /* This must be called with the setup/teardown functions so formatters is not NULL. */
     pcmk__assert_asserts(pcmk__output_new(NULL, "fake", NULL, NULL));
 }
 
 static void
 no_such_format(void **state) {
     pcmk__output_t *out = NULL;
 
     assert_int_equal(pcmk__output_new(&out, "fake", NULL, NULL), pcmk_rc_unknown_format);
 }
 
 static void
 create_fails(void **state) {
     pcmk__output_t *out = NULL;
 
     pcmk__mock_calloc = true;   // calloc() will return NULL
 
+    expect_value(__wrap_calloc, nmemb, 1);
+    expect_value(__wrap_calloc, size, sizeof(pcmk__output_t));
     assert_int_equal(pcmk__output_new(&out, "text", NULL, NULL), ENOMEM);
 
     pcmk__mock_calloc = false;  // Use real calloc()
 }
 
 static void
 fopen_fails(void **state) {
     pcmk__output_t *out = NULL;
 
     pcmk__mock_fopen = true;
+    expect_string(__wrap_fopen, pathname, "destfile");
+    expect_string(__wrap_fopen, mode, "w");
     will_return(__wrap_fopen, EPERM);
 
     assert_int_equal(pcmk__output_new(&out, "text", "destfile", NULL), EPERM);
 
     pcmk__mock_fopen = false;
 }
 
 static void
 init_fails(void **state) {
     pcmk__output_t *out = NULL;
 
     init_succeeds = false;
     assert_int_equal(pcmk__output_new(&out, "text", NULL, NULL), ENOMEM);
     init_succeeds = true;
 }
 
 static void
 everything_succeeds(void **state) {
     pcmk__output_t *out = NULL;
 
     assert_int_equal(pcmk__output_new(&out, "text", NULL, NULL), pcmk_rc_ok);
     assert_string_equal(out->fmt_name, "text");
     assert_ptr_equal(out->dest, stdout);
     assert_false(out->quiet);
     assert_non_null(out->messages);
     assert_string_equal(getenv("OCF_OUTPUT_FORMAT"), "text");
 
     pcmk__output_free(out);
 }
 
 static void
 no_fmt_name_given(void **state) {
     pcmk__output_t *out = NULL;
 
     assert_int_equal(pcmk__output_new(&out, NULL, NULL, NULL), pcmk_rc_ok);
     assert_string_equal(out->fmt_name, "text");
 
     pcmk__output_free(out);
 }
 
 int
 main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(empty_formatters),
         cmocka_unit_test_setup_teardown(invalid_params, setup, teardown),
         cmocka_unit_test_setup_teardown(no_such_format, setup, teardown),
         cmocka_unit_test_setup_teardown(create_fails, setup, teardown),
         cmocka_unit_test_setup_teardown(init_fails, setup, teardown),
         cmocka_unit_test_setup_teardown(fopen_fails, setup, teardown),
         cmocka_unit_test_setup_teardown(everything_succeeds, setup, teardown),
         cmocka_unit_test_setup_teardown(no_fmt_name_given, setup, teardown),
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/tests/procfs/pcmk__procfs_has_pids_false_test.c b/lib/common/tests/procfs/pcmk__procfs_has_pids_false_test.c
index 2f11c2d615..b9fa50fa7d 100644
--- a/lib/common/tests/procfs/pcmk__procfs_has_pids_false_test.c
+++ b/lib/common/tests/procfs/pcmk__procfs_has_pids_false_test.c
@@ -1,47 +1,55 @@
 /*
  * Copyright 2022 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 <crm/common/unittest_internal.h>
 
 #include "mock_private.h"
 
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
 
 #if SUPPORT_PROCFS
 
 static void
 no_pids(void **state)
 {
+    char path[PATH_MAX];
+
+    snprintf(path, PATH_MAX, "/proc/%u/exe", getpid());
+
     // Set readlink() errno and link contents (for /proc/PID/exe)
     pcmk__mock_readlink = true;
+
+    expect_string(__wrap_readlink, path, path);
+    expect_any(__wrap_readlink, buf);
+    expect_value(__wrap_readlink, bufsize, PATH_MAX - 1);
     will_return(__wrap_readlink, ENOENT);
     will_return(__wrap_readlink, NULL);
 
     assert_false(pcmk__procfs_has_pids());
 
     pcmk__mock_readlink = false;
 }
 
 #endif // SUPPORT_PROCFS
 
 int main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
 #if SUPPORT_PROCFS
         cmocka_unit_test(no_pids),
 #endif
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/tests/procfs/pcmk__procfs_has_pids_true_test.c b/lib/common/tests/procfs/pcmk__procfs_has_pids_true_test.c
index f9730a6baf..b15435e053 100644
--- a/lib/common/tests/procfs/pcmk__procfs_has_pids_true_test.c
+++ b/lib/common/tests/procfs/pcmk__procfs_has_pids_true_test.c
@@ -1,47 +1,55 @@
 /*
  * Copyright 2022 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 <crm/common/unittest_internal.h>
 
 #include "mock_private.h"
 
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
 
 #if SUPPORT_PROCFS
 
 static void
 has_pids(void **state)
 {
+    char path[PATH_MAX];
+
+    snprintf(path, PATH_MAX, "/proc/%u/exe", getpid());
+
     // Set readlink() errno and link contents (for /proc/PID/exe)
     pcmk__mock_readlink = true;
+
+    expect_string(__wrap_readlink, path, path);
+    expect_any(__wrap_readlink, buf);
+    expect_value(__wrap_readlink, bufsize, PATH_MAX - 1);
     will_return(__wrap_readlink, 0);
     will_return(__wrap_readlink, "/ok");
 
     assert_true(pcmk__procfs_has_pids());
 
     pcmk__mock_readlink = false;
 }
 
 #endif // SUPPORT_PROCFS
 
 int main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
 #if SUPPORT_PROCFS
         cmocka_unit_test(has_pids),
 #endif
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/tests/procfs/pcmk__procfs_pid2path_test.c b/lib/common/tests/procfs/pcmk__procfs_pid2path_test.c
index aaba7e29a7..11f01032fc 100644
--- a/lib/common/tests/procfs/pcmk__procfs_pid2path_test.c
+++ b/lib/common/tests/procfs/pcmk__procfs_pid2path_test.c
@@ -1,84 +1,96 @@
 /*
  * Copyright 2022 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 <crm/common/unittest_internal.h>
 
 #include "mock_private.h"
 
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
 
 #if SUPPORT_PROCFS
 
 static void
 no_exe_file(void **state)
 {
     char path[PATH_MAX];
 
     // Set readlink() errno and link contents
     pcmk__mock_readlink = true;
+
+    expect_string(__wrap_readlink, path, "/proc/1000/exe");
+    expect_value(__wrap_readlink, buf, path);
+    expect_value(__wrap_readlink, bufsize, sizeof(path) - 1);
     will_return(__wrap_readlink, ENOENT);
     will_return(__wrap_readlink, NULL);
 
     assert_int_equal(pcmk__procfs_pid2path(1000, path, sizeof(path)), ENOENT);
 
     pcmk__mock_readlink = false;
 }
 
 static void
 contents_too_long(void **state)
 {
     char path[10];
 
     // Set readlink() errno and link contents
     pcmk__mock_readlink = true;
+
+    expect_string(__wrap_readlink, path, "/proc/1000/exe");
+    expect_value(__wrap_readlink, buf, path);
+    expect_value(__wrap_readlink, bufsize, sizeof(path) - 1);
     will_return(__wrap_readlink, 0);
     will_return(__wrap_readlink, "/more/than/10/characters");
 
     assert_int_equal(pcmk__procfs_pid2path(1000, path, sizeof(path)),
                      ENAMETOOLONG);
 
     pcmk__mock_readlink = false;
 }
 
 static void
 contents_ok(void **state)
 {
     char path[PATH_MAX];
 
     // Set readlink() errno and link contents
     pcmk__mock_readlink = true;
+
+    expect_string(__wrap_readlink, path, "/proc/1000/exe");
+    expect_value(__wrap_readlink, buf, path);
+    expect_value(__wrap_readlink, bufsize, sizeof(path) - 1);
     will_return(__wrap_readlink, 0);
     will_return(__wrap_readlink, "/ok");
 
     assert_int_equal(pcmk__procfs_pid2path((pid_t) 1000, path, sizeof(path)),
                      pcmk_rc_ok);
     assert_string_equal(path, "/ok");
 
     pcmk__mock_readlink = false;
 }
 
 #endif // SUPPORT_PROCFS
 
 int main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
 #if SUPPORT_PROCFS
         cmocka_unit_test(no_exe_file),
         cmocka_unit_test(contents_too_long),
         cmocka_unit_test(contents_ok),
 #endif
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/tests/strings/pcmk__compress_test.c b/lib/common/tests/strings/pcmk__compress_test.c
index 5e131b768d..adf7e7a175 100644
--- a/lib/common/tests/strings/pcmk__compress_test.c
+++ b/lib/common/tests/strings/pcmk__compress_test.c
@@ -1,60 +1,66 @@
 /*
  * Copyright 2022 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 <crm/common/unittest_internal.h>
 
 #include "mock_private.h"
 
 #define SIMPLE_DATA "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
 
 const char *SIMPLE_COMPRESSED = "BZh41AY&SYO\x1ai";
 
 static void
 simple_compress(void **state)
 {
     char *result = calloc(1024, sizeof(char));
     unsigned int len;
 
     assert_int_equal(pcmk__compress(SIMPLE_DATA, 40, 0, &result, &len), pcmk_rc_ok);
     assert_memory_equal(result, SIMPLE_COMPRESSED, 13);
 }
 
 static void
 max_too_small(void **state)
 {
     char *result = calloc(1024, sizeof(char));
     unsigned int len;
 
     assert_int_equal(pcmk__compress(SIMPLE_DATA, 40, 10, &result, &len), pcmk_rc_error);
 }
 
 static void
 calloc_fails(void **state) {
     char *result = calloc(1024, sizeof(char));
     unsigned int len;
 
-    pcmk__mock_calloc = true;   // calloc() will return NULL
-    pcmk__assert_asserts(pcmk__compress(SIMPLE_DATA, 40, 0, &result, &len));
-    pcmk__mock_calloc = false;  // Use the real calloc()
+    pcmk__assert_asserts(
+        {
+            pcmk__mock_calloc = true;   // calloc() will return NULL
+            expect_value(__wrap_calloc, nmemb, (size_t) ((40 * 1.01) + 601));
+            expect_value(__wrap_calloc, size, sizeof(char));
+            pcmk__compress(SIMPLE_DATA, 40, 0, &result, &len);
+            pcmk__mock_calloc = false;  // Use the real calloc()
+        }
+    );
 }
 
 int
 main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(simple_compress),
         cmocka_unit_test(max_too_small),
         cmocka_unit_test(calloc_fails),
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/tests/strings/pcmk__str_update_test.c b/lib/common/tests/strings/pcmk__str_update_test.c
index 7d1b0cf780..e9c9220958 100644
--- a/lib/common/tests/strings/pcmk__str_update_test.c
+++ b/lib/common/tests/strings/pcmk__str_update_test.c
@@ -1,81 +1,86 @@
 /*
  * Copyright 2022 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 <crm/common/unittest_internal.h>
 
 #include "mock_private.h"
 
 static void
 update_null(void **state) {
     char *str = NULL;
 
     // These just make sure they don't crash
     pcmk__str_update(NULL, NULL);
     pcmk__str_update(NULL, "value");
 
     // Update an already NULL string to NULL
     pcmk__str_update(&str, NULL);
     assert_null(str);
 
     // Update an already allocated string to NULL
     str = strdup("hello");
     pcmk__str_update(&str, NULL);
     assert_null(str);
 }
 
 static void
 update_same(void **state) {
     char *str = NULL;
     char *saved = NULL;
 
     str = strdup("hello");
     saved = str;
     pcmk__str_update(&str, "hello");
     assert_ptr_equal(saved, str); // No free and reallocation
     free(str);
 }
 
 static void
 update_different(void **state) {
     char *str = NULL;
 
     str = strdup("hello");
     pcmk__str_update(&str, "world");
     assert_string_equal(str, "world");
     free(str);
 }
 
 static void
 strdup_fails(void **state) {
     char *str = NULL;
 
     str = strdup("hello");
 
-    pcmk__mock_strdup = true;       // strdup() will return NULL
-    pcmk__assert_asserts(pcmk__str_update(&str, "world"));
-    pcmk__mock_strdup = false;      // Use the real strdup()
+    pcmk__assert_asserts(
+        {
+            pcmk__mock_strdup = true;   // strdup() will return NULL
+            expect_string(__wrap_strdup, s, "world");
+            pcmk__str_update(&str, "world");
+            pcmk__mock_strdup = false;  // Use the real strdup()
+        }
+    );
 
     free(str);
 }
 
 int
 main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(update_null),
         cmocka_unit_test(update_same),
         cmocka_unit_test(update_different),
         cmocka_unit_test(strdup_fails),
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/tests/utils/crm_user_lookup_test.c b/lib/common/tests/utils/crm_user_lookup_test.c
index f40e318e25..89368cd9f0 100644
--- a/lib/common/tests/utils/crm_user_lookup_test.c
+++ b/lib/common/tests/utils/crm_user_lookup_test.c
@@ -1,108 +1,134 @@
 /*
  * Copyright 2022 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 <crm/common/unittest_internal.h>
 
+#include "crmcommon_private.h"
 #include "mock_private.h"
 
 #include <pwd.h>
 #include <sys/types.h>
 
 static void
 calloc_fails(void **state)
 {
     uid_t uid;
     gid_t gid;
 
     pcmk__mock_calloc = true;   // calloc() will return NULL
 
+    expect_value(__wrap_calloc, nmemb, 1);
+    expect_value(__wrap_calloc, size, PCMK__PW_BUFFER_LEN);
     assert_int_equal(crm_user_lookup("hauser", &uid, &gid), -ENOMEM);
 
     pcmk__mock_calloc = false;  // Use real calloc()
 }
 
 static void
 getpwnam_r_fails(void **state)
 {
     uid_t uid;
     gid_t gid;
 
     // Set getpwnam_r() return value and result parameter
     pcmk__mock_getpwnam_r = true;
+
+    expect_string(__wrap_getpwnam_r, name, "hauser");
+    expect_any(__wrap_getpwnam_r, pwd);
+    expect_any(__wrap_getpwnam_r, buf);
+    expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN);
+    expect_any(__wrap_getpwnam_r, result);
     will_return(__wrap_getpwnam_r, EIO);
     will_return(__wrap_getpwnam_r, NULL);
 
     assert_int_equal(crm_user_lookup("hauser", &uid, &gid), -EIO);
 
     pcmk__mock_getpwnam_r = false;
 }
 
 static void
 no_matching_pwent(void **state)
 {
     uid_t uid;
     gid_t gid;
 
     // Set getpwnam_r() return value and result parameter
     pcmk__mock_getpwnam_r = true;
+
+    expect_string(__wrap_getpwnam_r, name, "hauser");
+    expect_any(__wrap_getpwnam_r, pwd);
+    expect_any(__wrap_getpwnam_r, buf);
+    expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN);
+    expect_any(__wrap_getpwnam_r, result);
     will_return(__wrap_getpwnam_r, 0);
     will_return(__wrap_getpwnam_r, NULL);
 
     assert_int_equal(crm_user_lookup("hauser", &uid, &gid), -EINVAL);
 
     pcmk__mock_getpwnam_r = false;
 }
 
 static void
 entry_found(void **state)
 {
     uid_t uid;
     gid_t gid;
 
     /* We don't care about any of the other fields of the password entry, so just
      * leave them blank.
      */
     struct passwd returned_ent = { .pw_uid = 1000, .pw_gid = 1000 };
 
     /* Test getpwnam_r returning a valid passwd entry, but we don't pass uid or gid. */
 
     // Set getpwnam_r() return value and result parameter
     pcmk__mock_getpwnam_r = true;
+
+    expect_string(__wrap_getpwnam_r, name, "hauser");
+    expect_any(__wrap_getpwnam_r, pwd);
+    expect_any(__wrap_getpwnam_r, buf);
+    expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN);
+    expect_any(__wrap_getpwnam_r, result);
     will_return(__wrap_getpwnam_r, 0);
     will_return(__wrap_getpwnam_r, &returned_ent);
 
     assert_int_equal(crm_user_lookup("hauser", NULL, NULL), 0);
 
     /* Test getpwnam_r returning a valid passwd entry, and we do pass uid and gid. */
 
     // Set getpwnam_r() return value and result parameter
+    expect_string(__wrap_getpwnam_r, name, "hauser");
+    expect_any(__wrap_getpwnam_r, pwd);
+    expect_any(__wrap_getpwnam_r, buf);
+    expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN);
+    expect_any(__wrap_getpwnam_r, result);
     will_return(__wrap_getpwnam_r, 0);
     will_return(__wrap_getpwnam_r, &returned_ent);
 
     assert_int_equal(crm_user_lookup("hauser", &uid, &gid), 0);
     assert_int_equal(uid, 1000);
     assert_int_equal(gid, 1000);
 
     pcmk__mock_getpwnam_r = false;
 }
 
 int main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(calloc_fails),
         cmocka_unit_test(getpwnam_r_fails),
         cmocka_unit_test(no_matching_pwent),
         cmocka_unit_test(entry_found),
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/tests/utils/pcmk_daemon_user_test.c b/lib/common/tests/utils/pcmk_daemon_user_test.c
index d904a7ffb4..0442ad8242 100644
--- a/lib/common/tests/utils/pcmk_daemon_user_test.c
+++ b/lib/common/tests/utils/pcmk_daemon_user_test.c
@@ -1,77 +1,90 @@
 /*
  * Copyright 2022 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 <crm/common/unittest_internal.h>
 
+#include "crmcommon_private.h"
 #include "mock_private.h"
 
 #include <pwd.h>
 #include <sys/types.h>
 
 static void
 no_matching_pwent(void **state)
 {
     uid_t uid;
     gid_t gid;
 
     // Set getpwnam_r() return value and result parameter
     pcmk__mock_getpwnam_r = true;
+
+    expect_string(__wrap_getpwnam_r, name, "hacluster");
+    expect_any(__wrap_getpwnam_r, pwd);
+    expect_any(__wrap_getpwnam_r, buf);
+    expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN);
+    expect_any(__wrap_getpwnam_r, result);
     will_return(__wrap_getpwnam_r, ENOENT);
     will_return(__wrap_getpwnam_r, NULL);
 
     assert_int_equal(pcmk_daemon_user(&uid, &gid), -ENOENT);
 
     pcmk__mock_getpwnam_r = false;
 }
 
 static void
 entry_found(void **state)
 {
     uid_t uid;
     gid_t gid;
 
     /* We don't care about any of the other fields of the password entry, so just
      * leave them blank.
      */
     struct passwd returned_ent = { .pw_uid = 1000, .pw_gid = 1000 };
 
     /* Test getpwnam_r returning a valid passwd entry, but we don't pass uid or gid. */
 
     // Set getpwnam_r() return value and result parameter
     pcmk__mock_getpwnam_r = true;
+
+    expect_string(__wrap_getpwnam_r, name, "hacluster");
+    expect_any(__wrap_getpwnam_r, pwd);
+    expect_any(__wrap_getpwnam_r, buf);
+    expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN);
+    expect_any(__wrap_getpwnam_r, result);
     will_return(__wrap_getpwnam_r, 0);
     will_return(__wrap_getpwnam_r, &returned_ent);
 
     assert_int_equal(pcmk_daemon_user(NULL, NULL), 0);
 
     /* Test getpwnam_r returning a valid passwd entry, and we do pass uid and gid. */
 
     /* We don't need to call will_return() again because pcmk_daemon_user()
      * will have cached the uid/gid from the previous call and won't make
      * another call to getpwnam_r().
      */
     assert_int_equal(pcmk_daemon_user(&uid, &gid), 0);
     assert_int_equal(uid, 1000);
     assert_int_equal(gid, 1000);
 
     pcmk__mock_getpwnam_r = false;
 }
 
 int main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(no_matching_pwent),
         cmocka_unit_test(entry_found),
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/tests/utils/pcmk_hostname_test.c b/lib/common/tests/utils/pcmk_hostname_test.c
index 7faadee436..d20aabc6a9 100644
--- a/lib/common/tests/utils/pcmk_hostname_test.c
+++ b/lib/common/tests/utils/pcmk_hostname_test.c
@@ -1,60 +1,64 @@
 /*
  * Copyright 2021 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 <crm/common/unittest_internal.h>
 
 #include "mock_private.h"
 
 #include <sys/utsname.h>
 
 static void
 uname_succeeded_test(void **state)
 {
     char *retval;
 
     // Set uname() return value and buf parameter node name
     pcmk__mock_uname = true;
+
+    expect_any(__wrap_uname, buf);
     will_return(__wrap_uname, 0);
     will_return(__wrap_uname, "somename");
 
     retval = pcmk_hostname();
     assert_non_null(retval);
     assert_string_equal("somename", retval);
 
     free(retval);
 
     pcmk__mock_uname = false;
 }
 
 static void
 uname_failed_test(void **state)
 {
     // Set uname() return value and buf parameter node name
     pcmk__mock_uname = true;
+
+    expect_any(__wrap_uname, buf);
     will_return(__wrap_uname, -1);
     will_return(__wrap_uname, NULL);
 
     assert_null(pcmk_hostname());
 
     pcmk__mock_uname = false;
 }
 
 int
 main(int argc, char **argv)
 {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(uname_succeeded_test),
         cmocka_unit_test(uname_failed_test),
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }
diff --git a/lib/common/utils.c b/lib/common/utils.c
index a0446a4c81..6db9e00aab 100644
--- a/lib/common/utils.c
+++ b/lib/common/utils.c
@@ -1,597 +1,593 @@
 /*
  * Copyright 2004-2022 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>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/utsname.h>
 
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
 #include <stdlib.h>
 #include <limits.h>
 #include <pwd.h>
 #include <time.h>
 #include <libgen.h>
 #include <signal.h>
 #include <grp.h>
 
 #include <qb/qbdefs.h>
 
 #include <crm/crm.h>
 #include <crm/services.h>
 #include <crm/msg_xml.h>
 #include <crm/cib/internal.h>
 #include <crm/common/xml.h>
 #include <crm/common/util.h>
 #include <crm/common/ipc.h>
 #include <crm/common/iso8601.h>
 #include <crm/common/mainloop.h>
 #include <libxml2/libxml/relaxng.h>
 
 #include "crmcommon_private.h"
 
-#ifndef PW_BUFFER_LEN
-#  define PW_BUFFER_LEN		500
-#endif
-
 CRM_TRACE_INIT_DATA(common);
 
 gboolean crm_config_error = FALSE;
 gboolean crm_config_warning = FALSE;
 char *crm_system_name = NULL;
 
 bool
 pcmk__is_user_in_group(const char *user, const char *group)
 {
     struct group *grent;
     char **gr_mem;
 
     if (user == NULL || group == NULL) {
         return false;
     }
     
     setgrent();
     while ((grent = getgrent()) != NULL) {
         if (grent->gr_mem == NULL) {
             continue;
         }
 
         if(strcmp(group, grent->gr_name) != 0) {
             continue;
         }
 
         gr_mem = grent->gr_mem;
         while (*gr_mem != NULL) {
             if (!strcmp(user, *gr_mem++)) {
                 endgrent();
                 return true;
             }
         }
     }
     endgrent();
     return false;
 }
 
 int
 crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
 {
     int rc = pcmk_ok;
     char *buffer = NULL;
     struct passwd pwd;
     struct passwd *pwentry = NULL;
 
-    buffer = calloc(1, PW_BUFFER_LEN);
+    buffer = calloc(1, PCMK__PW_BUFFER_LEN);
     if (buffer == NULL) {
         return -ENOMEM;
     }
 
-    rc = getpwnam_r(name, &pwd, buffer, PW_BUFFER_LEN, &pwentry);
+    rc = getpwnam_r(name, &pwd, buffer, PCMK__PW_BUFFER_LEN, &pwentry);
     if (pwentry) {
         if (uid) {
             *uid = pwentry->pw_uid;
         }
         if (gid) {
             *gid = pwentry->pw_gid;
         }
         crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid);
 
     } else {
         rc = rc? -rc : -EINVAL;
         crm_info("User %s lookup: %s", name, pcmk_strerror(rc));
     }
 
     free(buffer);
     return rc;
 }
 
 /*!
  * \brief Get user and group IDs of pacemaker daemon user
  *
  * \param[out] uid  If non-NULL, where to store daemon user ID
  * \param[out] gid  If non-NULL, where to store daemon group ID
  *
  * \return pcmk_ok on success, -errno otherwise
  */
 int
 pcmk_daemon_user(uid_t *uid, gid_t *gid)
 {
     static uid_t daemon_uid;
     static gid_t daemon_gid;
     static bool found = false;
     int rc = pcmk_ok;
 
     if (!found) {
         rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid);
         if (rc == pcmk_ok) {
             found = true;
         }
     }
     if (found) {
         if (uid) {
             *uid = daemon_uid;
         }
         if (gid) {
             *gid = daemon_gid;
         }
     }
     return rc;
 }
 
 /*!
  * \internal
  * \brief Return the integer equivalent of a portion of a string
  *
  * \param[in]  text      Pointer to beginning of string portion
  * \param[out] end_text  This will point to next character after integer
  */
 static int
 version_helper(const char *text, const char **end_text)
 {
     int atoi_result = -1;
 
     CRM_ASSERT(end_text != NULL);
 
     errno = 0;
 
     if (text != NULL && text[0] != 0) {
         /* seemingly sacrificing const-correctness -- because while strtol
            doesn't modify the input, it doesn't want to artificially taint the
            "end_text" pointer-to-pointer-to-first-char-in-string with constness
            in case the input wasn't actually constant -- by semantic definition
            not a single character will get modified so it shall be perfectly
            safe to make compiler happy with dropping "const" qualifier here */
         atoi_result = (int) strtol(text, (char **) end_text, 10);
 
         if (errno == EINVAL) {
             crm_err("Conversion of '%s' %c failed", text, text[0]);
             atoi_result = -1;
         }
     }
     return atoi_result;
 }
 
 /*
  * version1 < version2 : -1
  * version1 = version2 :  0
  * version1 > version2 :  1
  */
 int
 compare_version(const char *version1, const char *version2)
 {
     int rc = 0;
     int lpc = 0;
     const char *ver1_iter, *ver2_iter;
 
     if (version1 == NULL && version2 == NULL) {
         return 0;
     } else if (version1 == NULL) {
         return -1;
     } else if (version2 == NULL) {
         return 1;
     }
 
     ver1_iter = version1;
     ver2_iter = version2;
 
     while (1) {
         int digit1 = 0;
         int digit2 = 0;
 
         lpc++;
 
         if (ver1_iter == ver2_iter) {
             break;
         }
 
         if (ver1_iter != NULL) {
             digit1 = version_helper(ver1_iter, &ver1_iter);
         }
 
         if (ver2_iter != NULL) {
             digit2 = version_helper(ver2_iter, &ver2_iter);
         }
 
         if (digit1 < digit2) {
             rc = -1;
             break;
 
         } else if (digit1 > digit2) {
             rc = 1;
             break;
         }
 
         if (ver1_iter != NULL && *ver1_iter == '.') {
             ver1_iter++;
         }
         if (ver1_iter != NULL && *ver1_iter == '\0') {
             ver1_iter = NULL;
         }
 
         if (ver2_iter != NULL && *ver2_iter == '.') {
             ver2_iter++;
         }
         if (ver2_iter != NULL && *ver2_iter == 0) {
             ver2_iter = NULL;
         }
     }
 
     if (rc == 0) {
         crm_trace("%s == %s (%d)", version1, version2, lpc);
     } else if (rc < 0) {
         crm_trace("%s < %s (%d)", version1, version2, lpc);
     } else if (rc > 0) {
         crm_trace("%s > %s (%d)", version1, version2, lpc);
     }
 
     return rc;
 }
 
 /*!
  * \brief Parse milliseconds from a Pacemaker interval specification
  *
  * \param[in] input  Pacemaker time interval specification (a bare number of
  *                   seconds, a number with a unit optionally with whitespace
  *                   before and/or after the number, or an ISO 8601 duration)
  *
  * \return Milliseconds equivalent of given specification on success (limited
  *         to the range of an unsigned integer), 0 if input is NULL,
  *         or 0 (and set errno to EINVAL) on error
  */
 guint
 crm_parse_interval_spec(const char *input)
 {
     long long msec = -1;
 
     errno = 0;
     if (input == NULL) {
         return 0;
 
     } else if (input[0] == 'P') {
         crm_time_t *period_s = crm_time_parse_duration(input);
 
         if (period_s) {
             msec = 1000 * crm_time_get_seconds(period_s);
             crm_time_free(period_s);
         }
 
     } else {
         msec = crm_get_msec(input);
     }
 
     if (msec < 0) {
         crm_warn("Using 0 instead of '%s'", input);
         errno = EINVAL;
         return 0;
     }
     return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec;
 }
 
 /*!
  * \internal
  * \brief Log a failed assertion
  *
  * \param[in] file              File making the assertion
  * \param[in] function          Function making the assertion
  * \param[in] line              Line of file making the assertion
  * \param[in] assert_condition  String representation of assertion
  */
 static void
 log_assertion_as(const char *file, const char *function, int line,
                  const char *assert_condition)
 {
     if (!pcmk__is_daemon) {
         crm_enable_stderr(TRUE); // Make sure command-line user sees message
     }
     crm_err("%s: Triggered fatal assertion at %s:%d : %s",
             function, file, line, assert_condition);
 }
 
 /* coverity[+kill] */
 /*!
  * \internal
  * \brief Log a failed assertion and abort
  *
  * \param[in] file              File making the assertion
  * \param[in] function          Function making the assertion
  * \param[in] line              Line of file making the assertion
  * \param[in] assert_condition  String representation of assertion
  *
  * \note This does not return
  */
 static _Noreturn void
 abort_as(const char *file, const char *function, int line,
          const char *assert_condition)
 {
     log_assertion_as(file, function, line, assert_condition);
     abort();
 }
 
 /* coverity[+kill] */
 /*!
  * \internal
  * \brief Handle a failed assertion
  *
  * When called by a daemon, fork a child that aborts (to dump core), otherwise
  * abort the current process.
  *
  * \param[in] file              File making the assertion
  * \param[in] function          Function making the assertion
  * \param[in] line              Line of file making the assertion
  * \param[in] assert_condition  String representation of assertion
  */
 static void
 fail_assert_as(const char *file, const char *function, int line,
                const char *assert_condition)
 {
     int status = 0;
     pid_t pid = 0;
 
     if (!pcmk__is_daemon) {
         abort_as(file, function, line, assert_condition); // does not return
     }
 
     pid = fork();
     switch (pid) {
         case -1: // Fork failed
             crm_warn("%s: Cannot dump core for non-fatal assertion at %s:%d "
                      ": %s", function, file, line, assert_condition);
             break;
 
         case 0: // Child process: just abort to dump core
             abort();
             break;
 
         default: // Parent process: wait for child
             crm_err("%s: Forked child [%d] to record non-fatal assertion at "
                     "%s:%d : %s", function, pid, file, line, assert_condition);
             crm_write_blackbox(SIGTRAP, NULL);
             do {
                 if (waitpid(pid, &status, 0) == pid) {
                     return; // Child finished dumping core
                 }
             } while (errno == EINTR);
             if (errno == ECHILD) {
                 // crm_mon ignores SIGCHLD
                 crm_trace("Cannot wait on forked child [%d] "
                           "(SIGCHLD is probably ignored)", pid);
             } else {
                 crm_err("Cannot wait on forked child [%d]: %s",
                         pid, pcmk_rc_str(errno));
             }
             break;
     }
 }
 
 /* coverity[+kill] */
 void
 crm_abort(const char *file, const char *function, int line,
           const char *assert_condition, gboolean do_core, gboolean do_fork)
 {
     if (!do_fork) {
         abort_as(file, function, line, assert_condition);
     } else if (do_core) {
         fail_assert_as(file, function, line, assert_condition);
     } else {
         log_assertion_as(file, function, line, assert_condition);
     }
 }
 
 /*!
  * \internal
  * \brief Convert the current process to a daemon process
  *
  * Fork a child process, exit the parent, create a PID file with the current
  * process ID, and close the standard input/output/error file descriptors.
  * Exit instead if a daemon is already running and using the PID file.
  *
  * \param[in] name     Daemon executable name
  * \param[in] pidfile  File name to use as PID file
  */
 void
 pcmk__daemonize(const char *name, const char *pidfile)
 {
     int rc;
     pid_t pid;
 
     /* Check before we even try... */
     rc = pcmk__pidfile_matches(pidfile, 1, name, &pid);
     if ((rc != pcmk_rc_ok) && (rc != ENOENT)) {
         crm_err("%s: already running [pid %lld in %s]",
                 name, (long long) pid, pidfile);
         printf("%s: already running [pid %lld in %s]\n",
                name, (long long) pid, pidfile);
         crm_exit(CRM_EX_ERROR);
     }
 
     pid = fork();
     if (pid < 0) {
         fprintf(stderr, "%s: could not start daemon\n", name);
         crm_perror(LOG_ERR, "fork");
         crm_exit(CRM_EX_OSERR);
 
     } else if (pid > 0) {
         crm_exit(CRM_EX_OK);
     }
 
     rc = pcmk__lock_pidfile(pidfile, name);
     if (rc != pcmk_rc_ok) {
         crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d",
                 pidfile, name, pcmk_rc_str(rc), rc);
         printf("Could not lock '%s' for %s: %s (%d)\n",
                pidfile, name, pcmk_rc_str(rc), rc);
         crm_exit(CRM_EX_ERROR);
     }
 
     umask(S_IWGRP | S_IWOTH | S_IROTH);
 
     close(STDIN_FILENO);
     pcmk__open_devnull(O_RDONLY);   // stdin (fd 0)
 
     close(STDOUT_FILENO);
     pcmk__open_devnull(O_WRONLY);   // stdout (fd 1)
 
     close(STDERR_FILENO);
     pcmk__open_devnull(O_WRONLY);   // stderr (fd 2)
 }
 
 char *
 crm_meta_name(const char *field)
 {
     int lpc = 0;
     int max = 0;
     char *crm_name = NULL;
 
     CRM_CHECK(field != NULL, return NULL);
     crm_name = crm_strdup_printf(CRM_META "_%s", field);
 
     /* Massage the names so they can be used as shell variables */
     max = strlen(crm_name);
     for (; lpc < max; lpc++) {
         switch (crm_name[lpc]) {
             case '-':
                 crm_name[lpc] = '_';
                 break;
         }
     }
     return crm_name;
 }
 
 const char *
 crm_meta_value(GHashTable * hash, const char *field)
 {
     char *key = NULL;
     const char *value = NULL;
 
     key = crm_meta_name(field);
     if (key) {
         value = g_hash_table_lookup(hash, key);
         free(key);
     }
 
     return value;
 }
 
 #ifdef HAVE_UUID_UUID_H
 #  include <uuid/uuid.h>
 #endif
 
 char *
 crm_generate_uuid(void)
 {
     unsigned char uuid[16];
     char *buffer = malloc(37);  /* Including NUL byte */
 
     uuid_generate(uuid);
     uuid_unparse(uuid, buffer);
     return buffer;
 }
 
 #ifdef HAVE_GNUTLS_GNUTLS_H
 void
 crm_gnutls_global_init(void)
 {
     signal(SIGPIPE, SIG_IGN);
     gnutls_global_init();
 }
 #endif
 
 /*!
  * \brief Get the local hostname
  *
  * \return Newly allocated string with name, or NULL (and set errno) on error
  */
 char *
 pcmk_hostname(void)
 {
     struct utsname hostinfo;
 
     return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename);
 }
 
 bool
 pcmk_str_is_infinity(const char *s) {
     return pcmk__str_any_of(s, CRM_INFINITY_S, CRM_PLUS_INFINITY_S, NULL);
 }
 
 bool
 pcmk_str_is_minus_infinity(const char *s) {
     return pcmk__str_eq(s, CRM_MINUS_INFINITY_S, pcmk__str_none);
 }
 
 /*!
  * \internal
  * \brief Sleep for given milliseconds
  *
  * \param[in] ms  Time to sleep
  *
  * \note The full time might not be slept if a signal is received.
  */
 void
 pcmk__sleep_ms(unsigned int ms)
 {
     // @TODO Impose a sane maximum sleep to avoid hanging a process for long
     //CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP);
 
     // Use sleep() for any whole seconds
     if (ms >= 1000) {
         sleep(ms / 1000);
         ms -= ms / 1000;
     }
 
     if (ms == 0) {
         return;
     }
 
 #if defined(HAVE_NANOSLEEP)
     // nanosleep() is POSIX-2008, so prefer that
     {
         struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) };
 
         nanosleep(&req, NULL);
     }
 #elif defined(HAVE_USLEEP)
     // usleep() is widely available, though considered obsolete
     usleep((useconds_t) ms);
 #else
     // Otherwise use a trick with select() timeout
     {
         struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms };
 
         select(0, NULL, NULL, NULL, &tv);
     }
 #endif
 }
diff --git a/lib/pengine/tests/status/pe_new_working_set_test.c b/lib/pengine/tests/status/pe_new_working_set_test.c
index 888d5d9ed0..bf08bdf3d5 100644
--- a/lib/pengine/tests/status/pe_new_working_set_test.c
+++ b/lib/pengine/tests/status/pe_new_working_set_test.c
@@ -1,50 +1,52 @@
 /*
  * Copyright 2022 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 <crm/common/unittest_internal.h>
 #include <crm/pengine/internal.h>
 
 #include "mock_private.h"
 
 static void
 calloc_fails(void **state) {
     pcmk__mock_calloc = true;   // calloc() will return NULL
 
+    expect_value(__wrap_calloc, nmemb, 1);
+    expect_value(__wrap_calloc, size, sizeof(pe_working_set_t));
     assert_null(pe_new_working_set());
 
     pcmk__mock_calloc = false;  // Use real calloc()
 }
 
 static void
 calloc_succeeds(void **state) {
     pe_working_set_t *data_set = pe_new_working_set();
 
     /* Nothing else to test about this function, as all it does is call
      * set_working_set_defaults which is also a public function and should
      * get its own unit test.
      */
     assert_non_null(data_set);
 
     /* Avoid calling pe_free_working_set here so we don't artificially
      * inflate the coverage numbers.
      */
     free(data_set);
 }
 
 int main(int argc, char **argv) {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(calloc_fails),
         cmocka_unit_test(calloc_succeeds),
     };
 
     cmocka_set_message_output(CM_OUTPUT_TAP);
     return cmocka_run_group_tests(tests, NULL, NULL);
 }