diff --git a/daemons/controld/controld_metadata.c b/daemons/controld/controld_metadata.c index 53d0b5ef33..019fe0b1c4 100644 --- a/daemons/controld/controld_metadata.c +++ b/daemons/controld/controld_metadata.c @@ -1,389 +1,389 @@ /* * Copyright 2017-2022 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. */ #include #include #include #include #include #include #include #if ENABLE_VERSIONED_ATTRS static regex_t *version_format_regex = NULL; #endif static void ra_param_free(void *param) { if (param) { struct ra_param_s *p = (struct ra_param_s *) param; if (p->rap_name) { free(p->rap_name); } free(param); } } static void metadata_free(void *metadata) { if (metadata) { struct ra_metadata_s *md = (struct ra_metadata_s *) metadata; if (md->ra_version) { free(md->ra_version); } g_list_free_full(md->ra_params, ra_param_free); free(metadata); } } GHashTable * metadata_cache_new(void) { return pcmk__strkey_table(free, metadata_free); } void metadata_cache_free(GHashTable *mdc) { if (mdc) { crm_trace("Destroying metadata cache with %d members", g_hash_table_size(mdc)); g_hash_table_destroy(mdc); } } void metadata_cache_reset(GHashTable *mdc) { if (mdc) { crm_trace("Resetting metadata cache with %d members", g_hash_table_size(mdc)); g_hash_table_remove_all(mdc); } } #if ENABLE_VERSIONED_ATTRS static gboolean valid_version_format(const char *version) { if (version == NULL) { return FALSE; } if (version_format_regex == NULL) { /* The OCF standard allows free-form versioning, but for our purposes of * versioned resource and operation attributes, we constrain it to * dot-separated numbers. Agents are still free to use other schemes, * but we can't determine attributes based on them. */ const char *regex_string = "^[[:digit:]]+([.][[:digit:]]+)*$"; version_format_regex = calloc(1, sizeof(regex_t)); regcomp(version_format_regex, regex_string, REG_EXTENDED | REG_NOSUB); /* If our regex doesn't compile, it's a bug on our side, so CRM_CHECK() * will give us a core dump to catch it. Pretend the version is OK * because we don't want our mistake to break versioned attributes * (which should only ever happen in a development branch anyway). */ CRM_CHECK(version_format_regex != NULL, return TRUE); } return regexec(version_format_regex, version, 0, NULL, 0) == 0; } #endif void metadata_cache_fini(void) { #if ENABLE_VERSIONED_ATTRS if (version_format_regex) { regfree(version_format_regex); free(version_format_regex); version_format_regex = NULL; } #endif } #if ENABLE_VERSIONED_ATTRS static char * ra_version_from_xml(xmlNode *metadata_xml, const lrmd_rsc_info_t *rsc) { const char *version = crm_element_value(metadata_xml, XML_ATTR_VERSION); if (version == NULL) { crm_debug("Metadata for %s:%s:%s does not specify a version", rsc->standard, rsc->provider, rsc->type); version = PCMK_DEFAULT_AGENT_VERSION; } else if (!valid_version_format(version)) { crm_notice("%s:%s:%s metadata version has unrecognized format", rsc->standard, rsc->provider, rsc->type); version = PCMK_DEFAULT_AGENT_VERSION; } else { crm_debug("Metadata for %s:%s:%s has version %s", rsc->standard, rsc->provider, rsc->type, version); } return strdup(version); } #endif static struct ra_param_s * ra_param_from_xml(xmlNode *param_xml) { const char *param_name = crm_element_value(param_xml, "name"); struct ra_param_s *p; p = calloc(1, sizeof(struct ra_param_s)); if (p == NULL) { crm_crit("Could not allocate memory for resource metadata"); return NULL; } p->rap_name = strdup(param_name); if (p->rap_name == NULL) { crm_crit("Could not allocate memory for resource metadata"); free(p); return NULL; } if (pcmk__xe_attr_is_true(param_xml, "reloadable")) { controld_set_ra_param_flags(p, ra_param_reloadable); } if (pcmk__xe_attr_is_true(param_xml, "unique")) { controld_set_ra_param_flags(p, ra_param_unique); } if (pcmk__xe_attr_is_true(param_xml, "private")) { controld_set_ra_param_flags(p, ra_param_private); } return p; } static void log_ra_ocf_version(const char *ra_key, const char *ra_ocf_version) { if (pcmk__str_empty(ra_ocf_version)) { crm_warn("%s does not advertise OCF version supported", ra_key); } else if (compare_version(ra_ocf_version, "2") >= 0) { crm_warn("%s supports OCF version %s (this Pacemaker version supports " PCMK_OCF_VERSION " and might not work properly with agent)", ra_key, ra_ocf_version); } else if (compare_version(ra_ocf_version, PCMK_OCF_VERSION) > 0) { crm_info("%s supports OCF version %s (this Pacemaker version supports " PCMK_OCF_VERSION " and might not use all agent features)", ra_key, ra_ocf_version); } else { crm_debug("%s supports OCF version %s", ra_key, ra_ocf_version); } } struct ra_metadata_s * metadata_cache_update(GHashTable *mdc, lrmd_rsc_info_t *rsc, const char *metadata_str) { char *key = NULL; xmlNode *metadata = NULL; xmlNode *match = NULL; struct ra_metadata_s *md = NULL; bool any_private_params = false; bool ocf1_1 = false; CRM_CHECK(mdc && rsc && metadata_str, return NULL); key = crm_generate_ra_key(rsc->standard, rsc->provider, rsc->type); if (!key) { - crm_crit("Could not allocate memory for resource metadata"); + crm_crit("Invalid resource agent standard or type provided"); goto err; } metadata = string2xml(metadata_str); if (!metadata) { crm_err("Metadata for %s:%s:%s is not valid XML", rsc->standard, rsc->provider, rsc->type); goto err; } md = calloc(1, sizeof(struct ra_metadata_s)); if (md == NULL) { crm_crit("Could not allocate memory for resource metadata"); goto err; } #if ENABLE_VERSIONED_ATTRS md->ra_version = ra_version_from_xml(metadata, rsc); #endif if (strcmp(rsc->standard, PCMK_RESOURCE_CLASS_OCF) == 0) { xmlChar *content = NULL; xmlNode *version_element = first_named_child(metadata, "version"); if (version_element != NULL) { content = xmlNodeGetContent(version_element); } log_ra_ocf_version(key, (const char *) content); if (content != NULL) { ocf1_1 = (compare_version((const char *) content, "1.1") >= 0); xmlFree(content); } } // Check supported actions match = first_named_child(metadata, "actions"); for (match = first_named_child(match, "action"); match != NULL; match = crm_next_same_xml(match)) { const char *action_name = crm_element_value(match, "name"); if (pcmk__str_eq(action_name, CRMD_ACTION_RELOAD_AGENT, pcmk__str_none)) { if (ocf1_1) { controld_set_ra_flags(md, key, ra_supports_reload_agent); } else { crm_notice("reload-agent action will not be used with %s " "because it does not support OCF 1.1 or later", key); } } else if (!ocf1_1 && pcmk__str_eq(action_name, CRMD_ACTION_RELOAD, pcmk__str_casei)) { controld_set_ra_flags(md, key, ra_supports_legacy_reload); } } // Build a parameter list match = first_named_child(metadata, "parameters"); for (match = first_named_child(match, "parameter"); match != NULL; match = crm_next_same_xml(match)) { const char *param_name = crm_element_value(match, "name"); if (param_name == NULL) { crm_warn("Metadata for %s:%s:%s has parameter without a name", rsc->standard, rsc->provider, rsc->type); } else { struct ra_param_s *p = ra_param_from_xml(match); if (p == NULL) { goto err; } if (pcmk_is_set(p->rap_flags, ra_param_private)) { any_private_params = true; } md->ra_params = g_list_prepend(md->ra_params, p); } } /* Newer resource agents support the "private" parameter attribute to * indicate sensitive parameters. For backward compatibility with older * agents, implicitly treat a few common names as private when the agent * doesn't specify any explicitly. */ if (!any_private_params) { for (GList *iter = md->ra_params; iter != NULL; iter = iter->next) { struct ra_param_s *p = iter->data; if (pcmk__str_any_of(p->rap_name, "password", "passwd", "user", NULL)) { controld_set_ra_param_flags(p, ra_param_private); } } } g_hash_table_replace(mdc, key, md); free_xml(metadata); return md; err: free(key); free_xml(metadata); metadata_free(md); return NULL; } /*! * \internal * \brief Get meta-data for a resource * * \param[in] lrm_state Use meta-data cache from this executor connection * \param[in] rsc Resource to get meta-data for * \param[in] source Allowed meta-data sources (bitmask of enum * controld_metadata_source_e values) * * \return Meta-data cache entry for given resource, or NULL if not available */ struct ra_metadata_s * controld_get_rsc_metadata(lrm_state_t *lrm_state, lrmd_rsc_info_t *rsc, uint32_t source) { struct ra_metadata_s *metadata = NULL; char *metadata_str = NULL; char *key = NULL; int rc = pcmk_ok; CRM_CHECK((lrm_state != NULL) && (rsc != NULL), return NULL); if (pcmk_is_set(source, controld_metadata_from_cache)) { key = crm_generate_ra_key(rsc->standard, rsc->provider, rsc->type); if (key != NULL) { metadata = g_hash_table_lookup(lrm_state->metadata_cache, key); free(key); } if (metadata != NULL) { return metadata; } } if (!pcmk_is_set(source, controld_metadata_from_agent)) { return NULL; } /* For now, we always collect resource agent meta-data via a local, * synchronous, direct execution of the agent. This has multiple issues: * the executor should execute agents, not the controller; meta-data for * Pacemaker Remote nodes should be collected on those nodes, not * locally; and the meta-data call shouldn't eat into the timeout of the * real action being performed. * * These issues are planned to be addressed by having the scheduler * schedule a meta-data cache check at the beginning of each transition. * Once that is working, this block will only be a fallback in case the * initial collection fails. */ rc = lrm_state_get_metadata(lrm_state, rsc->standard, rsc->provider, rsc->type, &metadata_str, 0); if (rc != pcmk_ok) { crm_warn("Failed to get metadata for %s (%s%s%s:%s): %s", rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), ((rsc->provider == NULL)? "" : rsc->provider), rsc->type, pcmk_strerror(rc)); return NULL; } metadata = metadata_cache_update(lrm_state->metadata_cache, rsc, metadata_str); free(metadata_str); if (metadata == NULL) { crm_warn("Failed to update metadata for %s (%s%s%s:%s)", rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), ((rsc->provider == NULL)? "" : rsc->provider), rsc->type); } return metadata; } diff --git a/doc/sphinx/Pacemaker_Development/helpers.rst b/doc/sphinx/Pacemaker_Development/helpers.rst index ecca7d2b67..f459c64249 100644 --- a/doc/sphinx/Pacemaker_Development/helpers.rst +++ b/doc/sphinx/Pacemaker_Development/helpers.rst @@ -1,485 +1,486 @@ C Development Helpers --------------------- .. index:: single: unit testing Refactoring ########### Pacemaker uses an optional tool called `coccinelle `_ to do automatic refactoring. coccinelle is a very complicated tool that can be difficult to understand, and the existing documentation makes it pretty tough to get started. Much of the documentation is either aimed at kernel developers or takes the form of grammars. However, it can apply very complex transformations across an entire source tree. This is useful for tasks like code refactoring, changing APIs (number or type of arguments, etc.), catching functions that should not be called, and changing existing patterns. coccinelle is driven by input scripts called `semantic patches `_ written in its own language. These scripts bear a passing resemblance to source code patches and tell coccinelle how to match and modify a piece of source code. They are stored in ``devel/coccinelle`` and each script either contains a single source transformation or several related transformations. In general, we try to keep these as simple as possible. In Pacemaker development, we use a couple targets in ``devel/Makefile.am`` to control coccinelle. The ``cocci`` target tries to apply each script to every Pacemaker source file, printing out any changes it would make to the console. The ``cocci-inplace`` target does the same but also makes those changes to the source files. A variety of warnings might also be printed. If you aren't working on a new script, these can usually be ignored. If you are working on a new coccinelle script, it can be useful (and faster) to skip everything else and only run the new script. The ``COCCI_FILES`` variable can be used for this: .. code-block:: none $ make -C devel COCCI_FILES=coccinelle/new-file.cocci cocci This variable is also used for preventing some coccinelle scripts in the Pacemaker source tree from running. Some scripts are disabled because they are not currently fully working or because they are there as templates. When adding a new script, remember to add it to this variable if it should always be run. One complication when writing coccinelle scripts is that certain Pacemaker source files may not use private functions (those whose name starts with ``pcmk__``). Handling this requires work in both the Makefile and in the coccinelle scripts. The Makefile deals with this by maintaining two lists of source files: those that may use private functions and those that may not. For those that may, a special argument (``-D internal``) is added to the coccinelle command line. This creates a virtual dependency named ``internal``. In the coccinelle scripts, those transformations that modify source code to use a private function also have a dependency on ``internal``. If that dependency was given on the command line, the transformation will be run. Otherwise, it will be skipped. This means that not all instances of an older style of code will be changed after running a given transformation. Some developer intervention is still necessary to know whether a source code block should have been changed or not. Probably the easiest way to learn how to use coccinelle is by following other people's scripts. In addition to the ones in the Pacemaker source directory, there's several others on the `coccinelle website `_. Sanitizers ########## gcc supports a variety of run-time checks called sanitizers. These can be used to catch programming errors with memory, race conditions, various undefined behavior conditions, and more. Because these are run-time checks, they should only be used during development and not in compiled packages or production code. Certain sanitizers cannot be combined with others because their run-time checks cause interfere. Instead of trying to figure out which combinations work, it is simplest to just enable one at a time. Each supported sanitizer requires an installed libray. In addition to just enabling the sanitizer, their use can be configured with environment variables. For example: .. code-block:: none $ ASAN_OPTIONS=verbosity=1:replace_str=true crm_mon -1R Pacemaker supports the following subset of gcc's sanitizers: +--------------------+-------------------------+----------+----------------------+ | Sanitizer | Configure Option | Library | Environment Variable | +====================+=========================+==========+======================+ | Address | --with-sanitizers=asan | libasan | ASAN_OPTIONS | +--------------------+-------------------------+----------+----------------------+ | Threads | --with-sanitizers=tsan | libtsan | TSAN_OPTIONS | +--------------------+-------------------------+----------+----------------------+ | Undefined behavior | --with-sanitizers=ubsan | libubsan | UBSAN_OPTIONS | +--------------------+-------------------------+----------+----------------------+ The undefined behavior sanitizer further supports suboptions that need to be given as CFLAGS when configuring pacemaker: .. code-block:: none $ CFLAGS=-fsanitize=integer-divide-by-zero ./configure --with-sanitizers=ubsan For more information, see the `gcc documentation `_ which also provides links to more information on each sanitizer. Unit Testing ############ Where possible, changes to the C side of Pacemaker should be accompanied by unit tests. Much of Pacemaker cannot effectively be unit tested (and there are other testing systems used for those parts), but the ``lib`` subdirectory is pretty easy to write tests for. Pacemaker uses the `cmocka unit testing framework `_ which looks a lot like other unit testing frameworks for C and should be fairly familiar. In addition to regular unit tests, cmocka also gives us the ability to use `mock functions `_ for unit testing functions that would otherwise be difficult to test. Organization ____________ Pay close attention to the organization and naming of test cases to ensure the unit tests continue to work as they should. Tests are spread throughout the source tree, alongside the source code they test. For instance, all the tests for the source code in ``lib/common/`` are in the ``lib/common/tests`` directory. If there is no ``tests`` subdirectory, there are no tests for that library yet. Under that directory, there is a ``Makefile.am`` and additional subdirectories. Each subdirectory contains the tests for a single library source file. For instance, all the tests for ``lib/common/strings.c`` are in the ``lib/common/tests/strings`` directory. Note that the test subdirectory does not have a ``.c`` suffix. If there is no test subdirectory, there are no tests for that file yet. Finally, under that directory, there is a ``Makefile.am`` and then various source files. Each of these source files tests the single function that it is named after. For instance, ``lib/common/tests/strings/pcmk__btoa_test.c`` tests the ``pcmk__btoa()`` function in ``lib/common/strings.c``. If there is no test source file, there are no tests for that function yet. The ``_test`` suffix on the test source file is important. All tests have this suffix, which means all the compiled test cases will also end with this suffix. That lets us ignore all the compiled tests with a single line in ``.gitignore``: .. code-block:: none /lib/*/tests/*/*_test Adding a test _____________ Testing a new function in an already testable source file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Follow these steps if you want to test a function in a source file where there are already other tested functions. For the purposes of this example, we will add a test for the ``pcmk__scan_port()`` function in ``lib/common/strings.c``. As you can see, there are already tests for other functions in this same file in the ``lib/common/tests/strings`` directory. * cd into ``lib/common/tests/strings`` * Add the new file to the the ``check_PROGRAMS`` variable in ``Makefile.am``, making it something like this: .. code-block:: none check_PROGRAMS = \ pcmk__add_word_test \ pcmk__btoa_test \ pcmk__scan_port_test * Create a new ``pcmk__scan_port_test.c`` file, copying the copyright and include boilerplate from another file in the same directory. * Continue with the steps in `Writing the test`_. Testing a function in a source file without tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Follow these steps if you want to test a function in a source file where there are not already other tested functions, but there are tests for other files in the same library. For the purposes of this example, we will add a test for the ``pcmk_acl_required()`` function in ``lib/common/acls.c``. At the time of this documentation being written, no tests existed for that source file, so there is no ``lib/common/tests/acls`` directory. * Add to ``AC_CONFIG_FILES`` in the top-level ``configure.ac`` file so the build process knows to use directory we're about to create. That variable would now look something like: .. code-block:: none dnl Other files we output AC_CONFIG_FILES(Makefile \ ... lib/common/tests/Makefile \ lib/common/tests/acls/Makefile \ lib/common/tests/agents/Makefile \ ... ) * cd into ``lib/common/tests`` * Add to the ``SUBDIRS`` variable in ``Makefile.am``, making it something like: .. code-block:: none SUBDIRS = agents acls cmdline flags operations strings utils xpath results * Create a new ``acls`` directory, copying the ``Makefile.am`` from some other directory. At this time, each ``Makefile.am`` is largely boilerplate with very little that needs to change from directory to directory. * cd into ``acls`` * Get rid of any existing values for ``check_PROGRAMS`` and set it to ``pcmk_acl_required_test`` like so: .. code-block:: none check_PROGRAMS = pcmk_acl_required_test * Double check that ``-I$(top_srcdir)/lib/common`` is present in ``AM_CPPFLAGS``. * Double check the settings of variables at the top of ``Makefile.am``. The following block should be present: .. code-block:: none - LDADD = $(top_builddir)/lib/common/libcrmcommon_test.la + LDADD = $(top_builddir)/lib/common/libcrmcommon_test.la \ + -lcmocka AM_CFLAGS = -DPCMK__UNIT_TESTING AM_LDFLAGS = $(LDFLAGS_WRAP) * Follow the steps in `Testing a new function in an already testable source file`_ to create the new ``pcmk_acl_required_test.c`` file. Testing a function in a library without tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Adding a test case for a function in a library that doesn't have any test cases to begin with is only slightly more complicated. In general, the steps are the same as for the previous section, except with an additional layer of directory creation. For the purposes of this example, we will add a test case for the ``lrmd_send_resource_alert()`` function in ``lib/lrmd/lrmd_alerts.c``. Note that this may not be a very good function or even library to write actual unit tests for. * Add to ``AC_CONFIG_FILES`` in the top-level ``configure.ac`` file so the build process knows to use directory we're about to create. That variable would now look something like: .. code-block:: none dnl Other files we output AC_CONFIG_FILES(Makefile \ ... lib/lrmd/Makefile \ lib/lrmd/tests/Makefile \ lib/services/Makefile \ ... ) * cd into ``lib/lrmd`` * Create a ``SUBDIRS`` variable in ``Makefile.am`` if it doesn't already exist. Most libraries should not have this variable already. .. code-block:: none SUBDIRS = tests * Create a new ``tests`` directory and add a ``Makefile.am`` with the following contents: .. code-block:: none SUBDIRS = lrmd_alerts * Follow the steps in `Testing a function in a source file without tests`_ to create the rest of the new directory structure. * Follow the steps in `Testing a new function in an already testable source file`_ to create the new ``lrmd_send_resource_alert_test.c`` file. Adding to an existing test case ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If all you need to do is add additional test cases to an existing file, none of the above work is necessary. All you need to do is find the test source file with the name matching your function and add to it and then follow the instructions in `Writing the test`_. Writing the test ________________ A test case file contains a fair amount of boilerplate. For this reason, it's usually easiest to just copy an existing file and adapt it to your needs. However, here's the basic structure: .. code-block:: c /* * 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 #include /* Put your test-specific includes here */ /* Put your test functions here */ int main(int argc, char **argv) { /* Register your test functions here */ cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } Each test-specific function should test one aspect of the library function, though it can include many assertions if there are many ways of testing that one aspect. For instance, there might be multiple ways of testing regular expression matching: .. code-block:: c static void regex(void **state) { const char *s1 = "abcd"; const char *s2 = "ABCD"; assert_true(pcmk__strcmp(NULL, "a..d", pcmk__str_regex) < 0); assert_true(pcmk__strcmp(s1, NULL, pcmk__str_regex) > 0); assert_int_equal(pcmk__strcmp(s1, "a..d", pcmk__str_regex), 0); } Each test-specific function must also be registered or it will not be called. This is done with ``cmocka_unit_test()`` in the ``main`` function: .. code-block:: c const struct CMUnitTest tests[] = { cmocka_unit_test(regex), }; Assertions __________ In addition to the `assertions provided by `_, ``unittest_internal.h`` also provides ``pcmk__assert_asserts``. This macro takes an expression and verifies that the expression aborts due to a failed call to ``CRM_ASSERT`` or some other similar function. It can be used like so: .. code-block:: c static void null_input_variables(void **state) { long long start, end; pcmk__assert_asserts(pcmk__parse_ll_range("1234", NULL, &end)); pcmk__assert_asserts(pcmk__parse_ll_range("1234", &start, NULL)); } Here, ``pcmk__parse_ll_range`` expects non-NULL for its second and third arguments. If one of those arguments is NULL, ``CRM_ASSERT`` will fail and the program will abort. ``pcmk__assert_asserts`` checks that the code would abort and the test passes. If the code does not abort, the test fails. Running _______ If you had to create any new files or directories, you will first need to run ``./configure`` from the top level of the source directory. This will regenerate the Makefiles throughout the tree. If you skip this step, your changes will be skipped and you'll be left wondering why the output doesn't match what you expected. To run the tests, simply run ``make check`` after previously building the source with ``make``. The test cases in each directory will be built and then run. This should not take long. If all the tests succeed, you will be back at the prompt. Scrolling back through the history, you should see lines like the following: .. code-block:: none PASS: pcmk__strcmp_test 1 - same_pointer PASS: pcmk__strcmp_test 2 - one_is_null PASS: pcmk__strcmp_test 3 - case_matters PASS: pcmk__strcmp_test 4 - case_insensitive PASS: pcmk__strcmp_test 5 - regex ============================================================================ Testsuite summary for pacemaker 2.1.0 ============================================================================ # TOTAL: 33 # PASS: 33 # SKIP: 0 # XFAIL: 0 # FAIL: 0 # XPASS: 0 # ERROR: 0 ============================================================================ make[7]: Leaving directory '/home/clumens/src/pacemaker/lib/common/tests/strings' The testing process will quit on the first failed test, and you will see lines like these: .. code-block:: none PASS: pcmk__scan_double_test 3 - trailing_chars FAIL: pcmk__scan_double_test 4 - typical_case PASS: pcmk__scan_double_test 5 - double_overflow PASS: pcmk__scan_double_test 6 - double_underflow ERROR: pcmk__scan_double_test - exited with status 1 PASS: pcmk__starts_with_test 1 - bad_input ============================================================================ Testsuite summary for pacemaker 2.1.0 ============================================================================ # TOTAL: 56 # PASS: 54 # SKIP: 0 # XFAIL: 0 # FAIL: 1 # XPASS: 0 # ERROR: 1 ============================================================================ See lib/common/tests/strings/test-suite.log Please report to users@clusterlabs.org ============================================================================ make[7]: *** [Makefile:1218: test-suite.log] Error 1 make[7]: Leaving directory '/home/clumens/src/pacemaker/lib/common/tests/strings' The failure is in ``lib/common/tests/strings/test-suite.log``: .. code-block:: none ERROR: pcmk__scan_double_test ============================= 1..6 ok 1 - empty_input_string PASS: pcmk__scan_double_test 1 - empty_input_string ok 2 - bad_input_string PASS: pcmk__scan_double_test 2 - bad_input_string ok 3 - trailing_chars PASS: pcmk__scan_double_test 3 - trailing_chars not ok 4 - typical_case FAIL: pcmk__scan_double_test 4 - typical_case # 0.000000 != 3.000000 # pcmk__scan_double_test.c:80: error: Failure! ok 5 - double_overflow PASS: pcmk__scan_double_test 5 - double_overflow ok 6 - double_underflow PASS: pcmk__scan_double_test 6 - double_underflow # not ok - tests ERROR: pcmk__scan_double_test - exited with status 1 At this point, you need to determine whether your test case is incorrect or whether the code being tested is incorrect. Fix whichever is wrong and continue. Debugging ######### gdb ___ If you use ``gdb`` for debugging, some helper functions are defined in ``devel/gdbhelpers``, which can be given to ``gdb`` using the ``-x`` option. From within the debugger, you can then invoke the ``pcmk`` command that will describe the helper functions available. diff --git a/lib/common/agents.c b/lib/common/agents.c index cde029f579..d2066c0299 100644 --- a/lib/common/agents.c +++ b/lib/common/agents.c @@ -1,209 +1,213 @@ /* * Copyright 2004-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 #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include /*! * \brief Get capabilities of a resource agent standard * * \param[in] standard Standard name * * \return Bitmask of enum pcmk_ra_caps values */ uint32_t pcmk_get_ra_caps(const char *standard) { /* @COMPAT This should probably be case-sensitive, but isn't, * for backward compatibility. */ if (standard == NULL) { return pcmk_ra_cap_none; } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF)) { return pcmk_ra_cap_provider | pcmk_ra_cap_params | pcmk_ra_cap_unique | pcmk_ra_cap_promotable; } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_STONITH)) { /* @COMPAT Stonith resources can't really be unique clones, but we've * allowed it in the past and have it in some scheduler regression tests * (which were likely never used as real configurations). * * @TODO Remove pcmk_ra_cap_unique at the next major schema version * bump, with a transform to remove globally-unique from the config. */ return pcmk_ra_cap_params | pcmk_ra_cap_unique | pcmk_ra_cap_stdin | pcmk_ra_cap_fence_params; } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD) || !strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) || !strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB) || !strcasecmp(standard, PCMK_RESOURCE_CLASS_UPSTART)) { /* Since service can map to LSB, systemd, or upstart, these should * have identical capabilities */ return pcmk_ra_cap_status; } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_NAGIOS)) { return pcmk_ra_cap_params; } return pcmk_ra_cap_none; } int pcmk__effective_rc(int rc) { int remapped_rc = rc; switch (rc) { case PCMK_OCF_DEGRADED: remapped_rc = PCMK_OCF_OK; break; case PCMK_OCF_DEGRADED_PROMOTED: remapped_rc = PCMK_OCF_RUNNING_PROMOTED; break; default: break; } return remapped_rc; } char * crm_generate_ra_key(const char *standard, const char *provider, const char *type) { - if (!standard && !provider && !type) { + bool std_empty = pcmk__str_empty(standard); + bool prov_empty = pcmk__str_empty(provider); + bool ty_empty = pcmk__str_empty(type); + + if (std_empty || ty_empty) { return NULL; } return crm_strdup_printf("%s%s%s:%s", - (standard? standard : ""), - (provider? ":" : ""), (provider? provider : ""), - (type? type : "")); + standard, + (prov_empty ? "" : ":"), (prov_empty ? "" : provider), + type); } /*! * \brief Parse a "standard[:provider]:type" agent specification * * \param[in] spec Agent specification * \param[out] standard Newly allocated memory containing agent standard (or NULL) * \param[out] provider Newly allocated memory containing agent provider (or NULL) * \param[put] type Newly allocated memory containing agent type (or NULL) * * \return pcmk_ok if the string could be parsed, -EINVAL otherwise * * \note It is acceptable for the type to contain a ':' if the standard supports * that. For example, systemd supports the form "systemd:UNIT@A:B". * \note It is the caller's responsibility to free the returned values. */ int crm_parse_agent_spec(const char *spec, char **standard, char **provider, char **type) { char *colon; CRM_CHECK(spec && standard && provider && type, return -EINVAL); *standard = NULL; *provider = NULL; *type = NULL; colon = strchr(spec, ':'); if ((colon == NULL) || (colon == spec)) { return -EINVAL; } *standard = strndup(spec, colon - spec); spec = colon + 1; if (pcmk_is_set(pcmk_get_ra_caps(*standard), pcmk_ra_cap_provider)) { colon = strchr(spec, ':'); if ((colon == NULL) || (colon == spec)) { free(*standard); return -EINVAL; } *provider = strndup(spec, colon - spec); spec = colon + 1; } if (*spec == '\0') { free(*standard); free(*provider); return -EINVAL; } *type = strdup(spec); return pcmk_ok; } /*! * \brief Check whether a given stonith parameter is handled by Pacemaker * * Return true if a given string is the name of one of the special resource * instance attributes interpreted directly by Pacemaker for stonith-class * resources. * * \param[in] param Parameter name to check * * \return true if \p param is a special fencing parameter */ bool pcmk_stonith_param(const char *param) { if (param == NULL) { return false; } if (pcmk__str_any_of(param, PCMK_STONITH_PROVIDES, PCMK_STONITH_STONITH_TIMEOUT, NULL)) { return true; } if (!pcmk__starts_with(param, "pcmk_")) { // Short-circuit common case return false; } if (pcmk__str_any_of(param, PCMK_STONITH_ACTION_LIMIT, PCMK_STONITH_DELAY_BASE, PCMK_STONITH_DELAY_MAX, PCMK_STONITH_HOST_ARGUMENT, PCMK_STONITH_HOST_CHECK, PCMK_STONITH_HOST_LIST, PCMK_STONITH_HOST_MAP, NULL)) { return true; } param = strchr(param + 5, '_'); // Skip past "pcmk_ACTION" return pcmk__str_any_of(param, "_action", "_timeout", "_retries", NULL); } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include bool crm_provider_required(const char *standard) { return pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider); } // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/common/mock.c b/lib/common/mock.c index bd4e4b86c7..e20e5b6890 100644 --- a/lib/common/mock.c +++ b/lib/common/mock.c @@ -1,264 +1,285 @@ /* * 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 #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 malloc() will return * NULL. */ bool pcmk__mock_calloc = false; void * __wrap_calloc(size_t nmemb, size_t size) { return pcmk__mock_calloc? NULL : __real_calloc(nmemb, size); } /* getenv() * * If pcmk__mock_getenv is set to true, later calls to getenv() must be preceded * by: * * will_return(__wrap_getenv, return_value); */ bool pcmk__mock_getenv = false; char * __wrap_getenv(const char *name) { return pcmk__mock_getenv? mock_ptr_type(char *) : __real_getenv(name); } +/* 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(); } } /* getpwnam_r() * * If pcmk__mock_getpwnam_r is set to true, later calls to getpwnam_r() must be * preceded by: * * will_return(__wrap_getpwnam_r, return_value); * will_return(__wrap_getpwnam_r, ptr_to_result_struct); */ 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); *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: * * will_return(__wrap_readlink, errno_to_set); * will_return(__wrap_readlink, link_contents); * * 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; 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. */ bool pcmk__mock_strdup = false; char * __wrap_strdup(const char *s) { return pcmk__mock_strdup? NULL : __real_strdup(s); } /* uname() * * If pcmk__mock_uname is set to true, later calls to uname() must be preceded * by: * * will_return(__wrap_uname, return_value); * will_return(__wrap_uname, node_name_for_buf_parameter_to_uname); */ 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 *); 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 e5be1e0707..e62f69aed4 100644 --- a/lib/common/mock_private.h +++ b/lib/common/mock_private.h @@ -1,59 +1,64 @@ /* * 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. */ #ifndef MOCK_PRIVATE__H # define MOCK_PRIVATE__H #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_getenv; char *__real_getenv(const char *name); char *__wrap_getenv(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/lib/common/tests/agents/Makefile.am b/lib/common/tests/agents/Makefile.am index a9b839b877..7e7368fecf 100644 --- a/lib/common/tests/agents/Makefile.am +++ b/lib/common/tests/agents/Makefile.am @@ -1,23 +1,27 @@ # # 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 General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # AM_CPPFLAGS = -I$(top_srcdir)/include \ -I$(top_builddir)/include \ -I$(top_srcdir)/lib/common LDADD = $(top_builddir)/lib/common/libcrmcommon_test.la \ -lcmocka AM_CFLAGS = -DPCMK__UNIT_TESTING AM_LDFLAGS = $(LDFLAGS_WRAP) include $(top_srcdir)/mk/tap.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = pcmk_stonith_param_test +check_PROGRAMS = crm_generate_ra_key_test \ + crm_parse_agent_spec_test \ + pcmk__effective_rc_test \ + pcmk_get_ra_caps_test \ + pcmk_stonith_param_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/agents/crm_generate_ra_key_test.c b/lib/common/tests/agents/crm_generate_ra_key_test.c new file mode 100644 index 0000000000..3f5b2582dd --- /dev/null +++ b/lib/common/tests/agents/crm_generate_ra_key_test.c @@ -0,0 +1,56 @@ +/* + * 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 + +#include +#include + +static void +all_params_null(void **state) { + assert_null(crm_generate_ra_key(NULL, NULL, NULL)); +} + +static void +some_params_null(void **state) { + char *retval; + + assert_null(crm_generate_ra_key("std", "prov", NULL)); + + retval = crm_generate_ra_key("std", NULL, "ty"); + assert_string_equal(retval, "std:ty"); + free(retval); + + assert_null(crm_generate_ra_key(NULL, "prov", "ty")); + assert_null(crm_generate_ra_key("std", NULL, NULL)); + assert_null(crm_generate_ra_key(NULL, "prov", NULL)); + assert_null(crm_generate_ra_key(NULL, NULL, "ty")); +} + +static void +no_params_null(void **state) { + char *retval; + + retval = crm_generate_ra_key("std", "prov", "ty"); + assert_string_equal(retval, "std:prov:ty"); + free(retval); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(all_params_null), + cmocka_unit_test(some_params_null), + cmocka_unit_test(no_params_null), + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/common/tests/agents/crm_parse_agent_spec_test.c b/lib/common/tests/agents/crm_parse_agent_spec_test.c new file mode 100644 index 0000000000..19b73c5b65 --- /dev/null +++ b/lib/common/tests/agents/crm_parse_agent_spec_test.c @@ -0,0 +1,95 @@ +/* + * 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 + +#include +#include + +static void +all_params_null(void **state) { + assert_int_equal(crm_parse_agent_spec(NULL, NULL, NULL, NULL), -EINVAL); + assert_int_equal(crm_parse_agent_spec("", NULL, NULL, NULL), -EINVAL); + assert_int_equal(crm_parse_agent_spec(":", NULL, NULL, NULL), -EINVAL); + assert_int_equal(crm_parse_agent_spec("::", NULL, NULL, NULL), -EINVAL); +} + +static void +no_prov_or_type(void **state) { + assert_int_equal(crm_parse_agent_spec("ocf", NULL, NULL, NULL), -EINVAL); + assert_int_equal(crm_parse_agent_spec("ocf:", NULL, NULL, NULL), -EINVAL); + assert_int_equal(crm_parse_agent_spec("ocf::", NULL, NULL, NULL), -EINVAL); +} + +static void +no_type(void **state) { + assert_int_equal(crm_parse_agent_spec("ocf:pacemaker:", NULL, NULL, NULL), -EINVAL); +} + +static void +get_std_and_ty(void **state) { + char *std = NULL; + char *prov = NULL; + char *ty = NULL; + + assert_int_equal(crm_parse_agent_spec("stonith:fence_xvm", &std, &prov, &ty), pcmk_ok); + assert_string_equal(std, "stonith"); + assert_null(prov); + assert_string_equal(ty, "fence_xvm"); + + free(std); + free(ty); +} + +static void +get_all_values(void **state) { + char *std = NULL; + char *prov = NULL; + char *ty = NULL; + + assert_int_equal(crm_parse_agent_spec("ocf:pacemaker:ping", &std, &prov, &ty), pcmk_ok); + assert_string_equal(std, "ocf"); + assert_string_equal(prov, "pacemaker"); + assert_string_equal(ty, "ping"); + + free(std); + free(prov); + free(ty); +} + +static void +get_systemd_values(void **state) { + char *std = NULL; + char *prov = NULL; + char *ty = NULL; + + assert_int_equal(crm_parse_agent_spec("systemd:UNIT@A:B", &std, &prov, &ty), pcmk_ok); + assert_string_equal(std, "systemd"); + assert_null(prov); + assert_string_equal(ty, "UNIT@A:B"); + + free(std); + free(ty); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(all_params_null), + cmocka_unit_test(no_prov_or_type), + cmocka_unit_test(no_type), + cmocka_unit_test(get_std_and_ty), + cmocka_unit_test(get_all_values), + cmocka_unit_test(get_systemd_values), + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/common/tests/agents/pcmk__effective_rc_test.c b/lib/common/tests/agents/pcmk__effective_rc_test.c new file mode 100644 index 0000000000..0d94dbd577 --- /dev/null +++ b/lib/common/tests/agents/pcmk__effective_rc_test.c @@ -0,0 +1,44 @@ +/* + * 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 + +#include +#include + +static void +pcmk__effective_rc_test(void **state) { + /* All other PCMK_OCF_* values after UNKNOWN are deprecated and no longer used, + * so probably not worth testing them. + */ + assert_int_equal(PCMK_OCF_OK, pcmk__effective_rc(PCMK_OCF_OK)); + assert_int_equal(PCMK_OCF_OK, pcmk__effective_rc(PCMK_OCF_DEGRADED)); + assert_int_equal(PCMK_OCF_RUNNING_PROMOTED, pcmk__effective_rc(PCMK_OCF_DEGRADED_PROMOTED)); + assert_int_equal(PCMK_OCF_UNKNOWN, pcmk__effective_rc(PCMK_OCF_UNKNOWN)); + + /* There's nothing that says pcmk__effective_rc is restricted to PCMK_OCF_* + * values. That's just how it's used. Let's check some values outside + * that range just to be sure. + */ + assert_int_equal(-1, pcmk__effective_rc(-1)); + assert_int_equal(255, pcmk__effective_rc(255)); + assert_int_equal(INT_MAX, pcmk__effective_rc(INT_MAX)); + assert_int_equal(INT_MIN, pcmk__effective_rc(INT_MIN)); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(pcmk__effective_rc_test), + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/common/tests/agents/pcmk_get_ra_caps_test.c b/lib/common/tests/agents/pcmk_get_ra_caps_test.c new file mode 100644 index 0000000000..fe7cfc36fb --- /dev/null +++ b/lib/common/tests/agents/pcmk_get_ra_caps_test.c @@ -0,0 +1,71 @@ +/* + * 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 + +#include +#include + +static void +ocf_standard(void **state) { + uint32_t expected = pcmk_ra_cap_provider | pcmk_ra_cap_params | + pcmk_ra_cap_unique | pcmk_ra_cap_promotable; + + assert_int_equal(pcmk_get_ra_caps("ocf"), expected); + assert_int_equal(pcmk_get_ra_caps("OCF"), expected); +} + +static void +stonith_standard(void **state) { + uint32_t expected = pcmk_ra_cap_params | pcmk_ra_cap_unique | + pcmk_ra_cap_stdin | pcmk_ra_cap_fence_params; + + assert_int_equal(pcmk_get_ra_caps("stonith"), expected); + assert_int_equal(pcmk_get_ra_caps("StOnItH"), expected); +} + +static void +service_standard(void **state) { + assert_int_equal(pcmk_get_ra_caps("systemd"), pcmk_ra_cap_status); + assert_int_equal(pcmk_get_ra_caps("SYSTEMD"), pcmk_ra_cap_status); + assert_int_equal(pcmk_get_ra_caps("service"), pcmk_ra_cap_status); + assert_int_equal(pcmk_get_ra_caps("SeRvIcE"), pcmk_ra_cap_status); + assert_int_equal(pcmk_get_ra_caps("lsb"), pcmk_ra_cap_status); + assert_int_equal(pcmk_get_ra_caps("LSB"), pcmk_ra_cap_status); + assert_int_equal(pcmk_get_ra_caps("upstart"), pcmk_ra_cap_status); + assert_int_equal(pcmk_get_ra_caps("uPsTaRt"), pcmk_ra_cap_status); +} + +static void +nagios_standard(void **state) { + assert_int_equal(pcmk_get_ra_caps("nagios"), pcmk_ra_cap_params); + assert_int_equal(pcmk_get_ra_caps("NAGios"), pcmk_ra_cap_params); +} + +static void +unknown_standard(void **state) { + assert_int_equal(pcmk_get_ra_caps("blahblah"), pcmk_ra_cap_none); + assert_int_equal(pcmk_get_ra_caps(""), pcmk_ra_cap_none); + assert_int_equal(pcmk_get_ra_caps(NULL), pcmk_ra_cap_none); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(ocf_standard), + cmocka_unit_test(stonith_standard), + cmocka_unit_test(service_standard), + cmocka_unit_test(nagios_standard), + cmocka_unit_test(unknown_standard), + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/common/tests/lists/Makefile.am b/lib/common/tests/lists/Makefile.am index 79c60e3d52..7281944344 100644 --- a/lib/common/tests/lists/Makefile.am +++ b/lib/common/tests/lists/Makefile.am @@ -1,25 +1,27 @@ # # 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 General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # AM_CPPFLAGS = -I$(top_srcdir)/include \ -I$(top_builddir)/include \ -I$(top_srcdir)/lib/common LDADD = $(top_builddir)/lib/common/libcrmcommon_test.la \ -lcmocka AM_CFLAGS = -DPCMK__UNIT_TESTING AM_LDFLAGS = $(LDFLAGS_WRAP) include $(top_srcdir)/mk/tap.mk # Add "_test" to the end of all test program names to simplify .gitignore. check_PROGRAMS = \ + pcmk__list_of_1_test \ + pcmk__list_of_multiple_test \ pcmk__subtract_lists_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/lists/pcmk__list_of_1_test.c b/lib/common/tests/lists/pcmk__list_of_1_test.c new file mode 100644 index 0000000000..916de82f59 --- /dev/null +++ b/lib/common/tests/lists/pcmk__list_of_1_test.c @@ -0,0 +1,53 @@ +/* + * 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 + +#include + +#include + +static void +empty_list(void **state) { + assert_false(pcmk__list_of_1(NULL)); +} + +static void +singleton_list(void **state) { + GList *lst = NULL; + + lst = g_list_append(lst, strdup("abc")); + assert_true(pcmk__list_of_1(lst)); + + g_list_free_full(lst, free); +} + +static void +longer_list(void **state) { + GList *lst = NULL; + + lst = g_list_append(lst, strdup("abc")); + lst = g_list_append(lst, strdup("xyz")); + assert_false(pcmk__list_of_1(lst)); + + g_list_free_full(lst, free); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(empty_list), + cmocka_unit_test(singleton_list), + cmocka_unit_test(longer_list), + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/common/tests/lists/pcmk__list_of_multiple_test.c b/lib/common/tests/lists/pcmk__list_of_multiple_test.c new file mode 100644 index 0000000000..cc78dac38b --- /dev/null +++ b/lib/common/tests/lists/pcmk__list_of_multiple_test.c @@ -0,0 +1,53 @@ +/* + * 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 + +#include + +#include + +static void +empty_list(void **state) { + assert_false(pcmk__list_of_multiple(NULL)); +} + +static void +singleton_list(void **state) { + GList *lst = NULL; + + lst = g_list_append(lst, strdup("abc")); + assert_false(pcmk__list_of_multiple(lst)); + + g_list_free_full(lst, free); +} + +static void +longer_list(void **state) { + GList *lst = NULL; + + lst = g_list_append(lst, strdup("abc")); + lst = g_list_append(lst, strdup("xyz")); + assert_true(pcmk__list_of_multiple(lst)); + + g_list_free_full(lst, free); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(empty_list), + cmocka_unit_test(singleton_list), + cmocka_unit_test(longer_list), + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/common/tests/strings/Makefile.am b/lib/common/tests/strings/Makefile.am index 8337d889d0..3f1e4d40e0 100644 --- a/lib/common/tests/strings/Makefile.am +++ b/lib/common/tests/strings/Makefile.am @@ -1,46 +1,47 @@ # # 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 General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # AM_CPPFLAGS = -I$(top_srcdir)/include \ -I$(top_builddir)/include \ -I$(top_srcdir)/lib/common LDADD = $(top_builddir)/lib/common/libcrmcommon_test.la \ -lcmocka AM_CFLAGS = -DPCMK__UNIT_TESTING AM_LDFLAGS = $(LDFLAGS_WRAP) include $(top_srcdir)/mk/tap.mk # Add "_test" to the end of all test program names to simplify .gitignore. check_PROGRAMS = \ crm_get_msec_test \ crm_is_true_test \ crm_str_to_boolean_test \ pcmk__add_word_test \ pcmk__btoa_test \ pcmk__char_in_any_str_test \ pcmk__compress_test \ pcmk__ends_with_test \ pcmk__guint_from_hash_test \ pcmk__numeric_strcasecmp_test \ pcmk__parse_ll_range_test \ + pcmk__s_test \ pcmk__scan_double_test \ pcmk__scan_min_int_test \ pcmk__scan_port_test \ pcmk__starts_with_test \ pcmk__str_any_of_test \ pcmk__str_in_list_test \ pcmk__str_table_dup_test \ pcmk__str_update_test \ pcmk__strcmp_test \ pcmk__strkey_table_test \ pcmk__strikey_table_test \ pcmk__trim_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/strings/pcmk__s_test.c b/lib/common/tests/strings/pcmk__s_test.c new file mode 100644 index 0000000000..1f31537f73 --- /dev/null +++ b/lib/common/tests/strings/pcmk__s_test.c @@ -0,0 +1,37 @@ +/* + * 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 + +#include + +static void +s_is_null(void **state) { + assert_null(pcmk__s(NULL, NULL)); + assert_string_equal(pcmk__s(NULL, ""), ""); + assert_string_equal(pcmk__s(NULL, "something"), "something"); +} + +static void +s_is_not_null(void **state) { + assert_string_equal(pcmk__s("something", NULL), "something"); + assert_string_equal(pcmk__s("something", "default"), "something"); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(s_is_null), + cmocka_unit_test(s_is_not_null), + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/common/tests/utils/Makefile.am b/lib/common/tests/utils/Makefile.am index b264a9a1e1..610254ef65 100644 --- a/lib/common/tests/utils/Makefile.am +++ b/lib/common/tests/utils/Makefile.am @@ -1,34 +1,35 @@ # # 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 General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # AM_CPPFLAGS = -I$(top_srcdir)/include \ -I$(top_builddir)/include \ -I$(top_srcdir)/lib/common LDADD = $(top_builddir)/lib/common/libcrmcommon_test.la \ -lcmocka AM_CFLAGS = -DPCMK__UNIT_TESTING AM_LDFLAGS = $(LDFLAGS_WRAP) include $(top_srcdir)/mk/tap.mk # Add "_test" to the end of all test program names to simplify .gitignore. check_PROGRAMS = \ compare_version_test \ crm_meta_name_test \ crm_meta_value_test \ crm_user_lookup_test \ pcmk_daemon_user_test \ pcmk_str_is_infinity_test \ - pcmk_str_is_minus_infinity_test + pcmk_str_is_minus_infinity_test \ + pcmk__getpid_s_test if WRAPPABLE_UNAME check_PROGRAMS += pcmk_hostname_test endif TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/utils/pcmk__getpid_s_test.c b/lib/common/tests/utils/pcmk__getpid_s_test.c new file mode 100644 index 0000000000..2015401e0f --- /dev/null +++ b/lib/common/tests/utils/pcmk__getpid_s_test.c @@ -0,0 +1,46 @@ +/* + * 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 + +#include + +#include "mock_private.h" + +#include +#include + +static void +pcmk__getpid_s_test(void **state) +{ + char *retval; + + // Set getpid() return value + pcmk__mock_getpid = true; + will_return(__wrap_getpid, 1234); + + retval = pcmk__getpid_s(); + assert_non_null(retval); + assert_string_equal("1234", retval); + + free(retval); + + pcmk__mock_getpid = false; +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(pcmk__getpid_s_test), + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/mk/tap.mk b/mk/tap.mk index ae53a8f727..c87cb5fa9f 100644 --- a/mk/tap.mk +++ b/mk/tap.mk @@ -1,27 +1,28 @@ # # 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 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 \ getenv \ + getpid \ getgrent \ getpwnam_r \ readlink \ setgrent \ strdup \ uname LDFLAGS_WRAP = $(foreach fn,$(WRAPPED),-Wl,--wrap=$(fn))