diff --git a/devel/Makefile.am b/devel/Makefile.am index 461424b7b8..94e6966bcf 100644 --- a/devel/Makefile.am +++ b/devel/Makefile.am @@ -1,308 +1,247 @@ # # Copyright 2020-2025 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 $(top_srcdir)/mk/common.mk include $(top_srcdir)/mk/release.mk -# Coccinelle is a tool that takes special patch-like files (called semantic patches) and -# applies them throughout a source tree. This is useful when refactoring, changing APIs, -# catching dangerous or incorrect code, and other similar tasks. It's not especially -# easy to write a semantic patch but most users should only be concerned about running -# the target and inspecting the results. -# -# Documentation (including examples, which are the most useful): -# https://coccinelle.gitlabpages.inria.fr/website/docs/ -# -# Run the "make cocci" target to just output what would be done, or "make cocci-inplace" -# to apply the changes to the source tree. -# -# COCCI_FILES may be set on the command line, if you want to test just a single file -# while it's under development. Otherwise, it is a list of all the files that are ready -# to be run. -# -# ref-passed-variables-inited.cocci seems to be returning some false positives around -# GHashTableIters, so it is disabled for the moment. -COCCI_FILES ?= coccinelle/string-any-of.cocci \ - coccinelle/string-empty.cocci \ - coccinelle/string-null-matches.cocci \ - coccinelle/use-func.cocci - - -dist_noinst_SCRIPTS = coccinelle/test/testrunner.sh -EXTRA_DIST = README \ - gdbhelpers \ - $(COCCI_FILES) \ - coccinelle/ref-passed-variables-inited.cocci \ - coccinelle/rename-fn.cocci \ - coccinelle/test/ref-passed-variables-inited.input.c \ - coccinelle/test/ref-passed-variables-inited.output - -# Any file in this list is allowed to use any of the pcmk__ internal functions. -# Coccinelle can use any transformation that depends on "internal" to rewrite -# code to use the internal functions. -MAY_USE_INTERNAL_FILES = $(shell find .. -path "../lib/*.c" -o -path "../lib/*private.h" -o -path "../tools/*.c" -o -path "../daemons/*.c" -o -path '../include/pcmki/*h' -o -name '*internal.h') - -# And then any file in this list is public API, which may not use internal -# functions. Thus, only those transformations that do not depend on "internal" -# may be applied. -OTHER_FILES = $(shell find ../include -name '*h' -a \! -name '*internal.h' -a \! -path '../include/pcmki/*') - -.PHONY: cocci -cocci: - -for cf in $(COCCI_FILES); do \ - for f in $(MAY_USE_INTERNAL_FILES); do \ - spatch $(_SPATCH_FLAGS) -D internal --very-quiet --local-includes --preprocess --sp-file $$cf $$f; \ - done ; \ - for f in $(OTHER_FILES); do \ - spatch $(_SPATCH_FLAGS) --very-quiet --local-includes --preprocess --sp-file $$cf $$f; \ - done ; \ - done - -.PHONY: cocci-inplace -cocci-inplace: - $(MAKE) $(AM_MAKEFLAGS) _SPATCH_FLAGS=--in-place cocci - -.PHONY: cocci-test -cocci-test: - for f in coccinelle/test/*.c; do \ - coccinelle/test/testrunner.sh $$f; \ - done +EXTRA_DIST = README \ + gdbhelpers # # Static analysis # ## clang # See scan-build(1) for possible checkers (leave empty to use default set) CLANG_checkers ?= .PHONY: clang clang: OUT=$$(cd $(top_builddir) \ && scan-build $(CLANG_checkers:%=-enable-checker %) \ $(MAKE) $(AM_MAKEFLAGS) CFLAGS="-std=c99 $(CFLAGS)" \ clean all 2>&1); \ REPORT=$$(echo "$$OUT" \ | sed -n -e "s/.*'scan-view \(.*\)'.*/\1/p"); \ [ -z "$$REPORT" ] && echo "$$OUT" || scan-view "$$REPORT" ## coverity # Aggressiveness (low, medium, or high) COVLEVEL ?= low # Generated outputs COVERITY_DIR = $(abs_top_builddir)/coverity-$(TAG) COVTAR = $(abs_top_builddir)/$(PACKAGE)-coverity-$(TAG).tgz COVEMACS = $(abs_top_builddir)/$(TAG).coverity COVHTML = $(COVERITY_DIR)/output/errors # Coverity outputs are phony so they get rebuilt every invocation .PHONY: $(COVERITY_DIR) $(COVERITY_DIR): coverity-clean $(MAKE) $(AM_MAKEFLAGS) -C $(top_builddir) init core-clean $(AM_V_GEN)cd $(top_builddir) \ && cov-build --dir "$@" $(MAKE) $(AM_MAKEFLAGS) core # Public coverity instance .PHONY: $(COVTAR) $(COVTAR): $(COVERITY_DIR) $(AM_V_GEN)tar czf "$@" --transform="s@.*$(TAG)@cov-int@" "$<" .PHONY: coverity coverity: $(COVTAR) @echo "Now go to https://scan.coverity.com/users/sign_in and upload:" @echo " $(COVTAR)" @echo "then make clean at the top level" # Licensed coverity instance # # The prerequisites are a little hacky; rather than actually required, some # of them are designed so that things execute in the proper order (which is # not the same as GNU make's order-only prerequisites). .PHONY: coverity-analyze coverity-analyze: $(COVERITY_DIR) @echo "" @echo "Analyzing (waiting for coverity license if necessary) ..." cd $(top_builddir) && cov-analyze --dir "$<" --wait-for-license \ --security --aggressiveness-level "$(COVLEVEL)" .PHONY: $(COVEMACS) $(COVEMACS): coverity-analyze $(AM_V_GEN)cd $(top_builddir) \ && cov-format-errors --dir "$(COVERITY_DIR)" --emacs-style > "$@" .PHONY: $(COVHTML) $(COVHTML): $(COVEMACS) $(AM_V_GEN)cd $(top_builddir) \ && cov-format-errors --dir "$(COVERITY_DIR)" --html-output "$@" .PHONY: coverity-corp coverity-corp: $(COVHTML) $(MAKE) $(AM_MAKEFLAGS) -C $(top_builddir) core-clean @echo "Done. See:" @echo " file://$(COVHTML)/index.html" @echo "When no longer needed, make coverity-clean" # Remove all outputs regardless of tag .PHONY: coverity-clean coverity-clean: -rm -rf "$(abs_top_builddir)"/coverity-* \ "$(abs_top_builddir)"/$(PACKAGE)-coverity-*.tgz \ "$(abs_top_builddir)"/*.coverity ## cppcheck GLIB_CFLAGS ?= $(pkg-config --cflags glib-2.0) GLIB_INCL_DEF_CFLAGS = $(shell echo $(GLIB_CFLAGS) \ | tr ' ' '\n' | grep '^-[IDU]' | paste -d ' ') # Use CPPCHECK_ARGS to pass extra cppcheck options, e.g.: # --enable={warning,style,performance,portability,information,all} # --inconclusive --std=posix # -DDEFAULT_CONCURRENT_FENCING_TRUE CPPCHECK_ARGS ?= CPPCHECK_DIRS = replace lib daemons tools CPPCHECK_OUT = $(abs_top_builddir)/cppcheck.out .PHONY: cppcheck cppcheck: cppcheck $(CPPCHECK_ARGS) -I $(top_srcdir)/include \ -I $(top_srcdir)/lib/common \ --check-level=exhaustive \ --include=/usr/include/qb/qblog.h \ --output-file=$(CPPCHECK_OUT) \ --max-configs=30 --inline-suppr -q \ --library=posix --library=gnu --library=gtk \ $(GLIB_INCL_DEF_CFLAGS) -D__GNUC__ \ $(foreach dir,$(CPPCHECK_DIRS),$(top_srcdir)/$(dir)) @echo "Done: See $(CPPCHECK_OUT)" @echo "When no longer needed, make cppcheck-clean" .PHONY: cppcheck-clean cppcheck-clean: -rm -f "$(CPPCHECK_OUT)" # # Coverage/profiling # COVERAGE_DIR = $(abs_top_builddir)/coverage # Check coverage of unit tests .PHONY: coverage coverage: coverage-partial-clean cd $(top_builddir) \ && $(MAKE) $(AM_MAKEFLAGS) \ && lcov --capture --no-external --exclude='*_test.c' --initial \ --directory lib --ignore-errors=unused \ --output-file pacemaker_base.info \ && $(MAKE) $(AM_MAKEFLAGS) check \ && lcov --capture --no-external --exclude='*_test.c' \ --directory lib --ignore-errors=unused \ --output-file pacemaker_test.info \ && lcov --add-tracefile pacemaker_base.info \ --add-tracefile pacemaker_test.info \ --output-file pacemaker_total.info \ && genhtml --output-directory $(COVERAGE_DIR) \ --show-details --title "Pacemaker library code coverage"\ pacemaker_total.info # Check coverage of CLI regression tests .PHONY: coverage-cts coverage-cts: coverage-partial-clean cd $(top_builddir) \ && $(MAKE) $(AM_MAKEFLAGS) \ && lcov --capture --no-external --initial --directory tools \ --output-file pacemaker_base.info \ && cts/cts-cli \ && lcov --capture --no-external --directory tools \ --output-file pacemaker_test.info \ && lcov --add-tracefile pacemaker_base.info \ --add-tracefile pacemaker_test.info \ --output-file pacemaker_total.info \ && genhtml --output-directory $(COVERAGE_DIR) \ --show-details --title "Pacemaker tools code coverage" \ pacemaker_total.info # Remove coverage-related files that aren't needed across runs .PHONY: coverage-partial-clean coverage-partial-clean: -rm -f $(top_builddir)/pacemaker_*.info -rm -rf $(COVERAGE_DIR) -find $(top_builddir) -name "*.gcda" -exec rm -f \{\} \; # This target removes all coverage-related files. It is only to be run when # done with coverage analysis and you are ready to go back to normal development, # starting with re-running ./configure. It is not to be run in between # "make coverage" runs. # # In particular, the *.gcno files are generated when the source is built. # Removing those files will break "make coverage" until the whole source tree # has been built and the *.gcno files generated again. .PHONY: coverage-clean coverage-clean: coverage-partial-clean -find $(top_builddir) -name "*.gcno" -exec rm -f \{\} \; # Automatic code formatting - this makes use of clang-format and the .clang-format # config file. It's based on GNU coding, but heavily modified for our needs # and to reflect the C coding guidelines documentation. # Limit clang-format to these directories INDENT_DIRS ?= . # Extra options to pass to clang-format INDENT_OPTS ?= .PHONY: indent indent: find $(INDENT_DIRS) -type f -name "*.[ch]" \ -exec clang-format $(INDENT_OPTS) \{\} \; # # Check whether copyrights have been updated appropriately # (Set COMMIT to desired commit or commit range to check, defaulting to HEAD, # or set it empty to check uncommitted changes) # YEAR = $(shell date +%Y) MODIFIED_FILES = $(shell case "$(COMMIT)" in \ [0-9a-f]*$(rparen) \ git diff-tree --no-commit-id \ --name-only "$(COMMIT)" -r ;; \ *$(rparen) \ cd "$(top_srcdir)"; \ git ls-files --modified ;; \ esac) .PHONY: copyright copyright: @cd "$(top_srcdir)" && for file in $(MODIFIED_FILES); do \ if ! grep 'opyright .*$(YEAR).* Pacemaker' "$$file" \ >/dev/null 2>&1; then \ echo "$$file"; \ fi; \ done # # Scratch file for ad-hoc testing # EXTRA_PROGRAMS = scratch nodist_scratch_SOURCES = scratch.c scratch_LDADD = $(top_builddir)/lib/common/libcrmcommon.la .PHONY: clean-local clean-local: coverage-clean coverity-clean cppcheck-clean -rm -f $(EXTRA_PROGRAMS) diff --git a/devel/coccinelle/ref-passed-variables-inited.cocci b/devel/coccinelle/ref-passed-variables-inited.cocci deleted file mode 100644 index 0d19ff756d..0000000000 --- a/devel/coccinelle/ref-passed-variables-inited.cocci +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2019-2020 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. - * - * - * We require each local variable that - * - * - is passed to a function through a dereference (suggesting it serves - * possibly also or merely as one of the output value propagators seperate - * from actual return value if employed at all) and - * - * - is then subsequently reused (possibly naively expecting it will always - * have been initialized (in said function at latest) further in its scope, - * - * to _always_ be assuredly initialized to some determined value, so as to - * prevent a risk of accidentally accessing unspecified value subsequent - * to the return from the considered function, which might not have set - * that variable at all, lest it would touch it at all. - */ - -virtual internal - -@ref_passed_variables_inited exists@ -identifier f_init, f_consume, var; -type T; -expression E, E_propagate; -@@ - - T -- var -+ var /*FIXME:initialize me*/ - ; - ... when != var = E - f_init(..., &var, ...) - ... when != var = E -( - return var; -| - f_consume(..., var, ...) -| - E_propagate = var -| - &var -| - *var -) diff --git a/devel/coccinelle/rename-fn.cocci b/devel/coccinelle/rename-fn.cocci deleted file mode 100644 index 5bce9b736c..0000000000 --- a/devel/coccinelle/rename-fn.cocci +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 General Public License version 2 - * or later (GPLv2+) WITHOUT ANY WARRANTY. - */ - -/* - * Rename a function or macro. This is here as a template; replace "old" and - * "new" below with the old and new names, run in the devel directory: - * - * make COCCI_FILES=coccinelle/rename-fn.cocci cocci-inplace - * - * then revert the file before committing. - */ - -virtual internal - -@@ expression E; @@ -- old(E) -+ new(E) diff --git a/devel/coccinelle/rename-struct-member.cocci b/devel/coccinelle/rename-struct-member.cocci deleted file mode 100644 index f2e3bec85a..0000000000 --- a/devel/coccinelle/rename-struct-member.cocci +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2024 the Pacemaker project contributors - * - * The version control history for this file may have further details. - * - * This source code is licensed under the GNU General Public License version 2 - * or later (GPLv2+) WITHOUT ANY WARRANTY. - */ - -/* - * Rename a struct member. This is here as a template; replace the capitalized - * names below as appropriate. This assumes a struct with a single typedef - * alias; modify appropriately if not. Then run in the devel directory: - * - * make COCCI_FILES=coccinelle/rename-struct-member.cocci cocci-inplace - * - * then revert the file before committing. - * - * This does not handle the member definition in the struct itself, nor uses in - * macros or when nested in another struct. - */ - -virtual internal - -@@ -struct STRUCT_NAME s; -@@ - -- s.OLD_NAME -+ s.NEW_NAME - -@@ -struct STRUCT_NAME *sp; -@@ - -- sp->OLD_NAME -+ sp->NEW_NAME - -@@ -TYPE_ALIAS a; -@@ - -- a.OLD_NAME -+ a.NEW_NAME - -@@ -TYPE_ALIAS *ap; -@@ - -- ap->OLD_NAME -+ ap->NEW_NAME diff --git a/devel/coccinelle/rename-var.cocci b/devel/coccinelle/rename-var.cocci deleted file mode 100644 index 65bba80649..0000000000 --- a/devel/coccinelle/rename-var.cocci +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 General Public License version 2 - * or later (GPLv2+) WITHOUT ANY WARRANTY. - */ - -/* - * Rename a variable. This is here as a template; replace "old" and - * "new" below with the old and new names. Here is an example: - * - * @@ @@ - * - xml_private_flags - * + pcmk__xml_flags - * - * Run in the devel directory: - * - * make COCCI_FILES=coccinelle/rename-var.cocci cocci-inplace - * - * then revert the file before committing. - */ - -virtual internal - -@@ @@ -- old -+ new diff --git a/devel/coccinelle/string-any-of.cocci b/devel/coccinelle/string-any-of.cocci deleted file mode 100644 index ef1a2e96dd..0000000000 --- a/devel/coccinelle/string-any-of.cocci +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2020 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. - * - * Catch string comparisons where the pcmk__str_any_of function could be used - * instead. Note that we are only catching uses involving identifiers (not - * expressions), but I think this is probably fine - we are likely not using - * the same expression multiple times in a single line of code. If some are - * found, it's easy enough to add another block here. - */ - -virtual internal - -@ any_of_1 depends on internal @ -expression test_str, str, new_str; -identifier I =~ "pcmk__str_none"; -@@ -- pcmk__str_eq(test_str, str, I) || pcmk__str_eq(test_str, new_str, I) -+ pcmk__str_any_of(test_str, str, new_str, NULL) - -@ any_of_2 depends on internal @ -expression test_str, str, new_str; -identifier I =~ "pcmk__str_casei"; -@@ -- pcmk__str_eq(test_str, str, I) || pcmk__str_eq(test_str, new_str, I) -+ pcmk__strcase_any_of(test_str, str, new_str, NULL) - -@ any_of_3 depends on internal @ -expression test_str, new_str; -expression list strs; -identifier I =~ "pcmk__str_none"; -@@ -- pcmk__str_any_of(test_str, strs, NULL) || pcmk__str_eq(test_str, new_str, I) -+ pcmk__str_any_of(test_str, strs, new_str, NULL) - -@ any_of_4 depends on internal @ -expression test_str, new_str; -expression list strs; -identifier I =~ "pcmk__str_casei"; -@@ -- pcmk__strcase_any_of(test_str, strs, NULL) || pcmk__str_eq(test_str, new_str, I) -+ pcmk__strcase_any_of(test_str, strs, new_str, NULL) - -@ none_of_1 depends on internal @ -expression test_str, str, new_str; -identifier I =~ "pcmk__str_none"; -@@ -- !pcmk__str_eq(test_str, str, I) && !pcmk__str_eq(test_str, new_str, I) -+ !pcmk__str_any_of(test_str, str, new_str, NULL) - -@ none_of_2 depends on internal @ -expression test_str, str, new_str; -identifier I =~ "pcmk__str_casei"; -@@ -- !pcmk__str_eq(test_str, str, I) && !pcmk__str_eq(test_str, new_str, I) -+ !pcmk__strcase_any_of(test_str, str, new_str, NULL) - -@ none_of_3 depends on internal @ -expression test_str, new_str; -expression list strs; -identifier I =~ "pcmk__str_none"; -@@ -- !pcmk__str_any_of(test_str, strs, NULL) && !pcmk__str_eq(test_str, new_str, I) -+ !pcmk__str_any_of(test_str, strs, new_str, NULL) - -@ none_of_4 depends on internal @ -expression test_str, new_str; -expression list strs; -identifier I =~ "pcmk__str_casei"; -@@ -- !pcmk__strcase_any_of(test_str, strs, NULL) && !pcmk__str_eq(test_str, new_str, I) -+ !pcmk__strcase_any_of(test_str, strs, new_str, NULL) diff --git a/devel/coccinelle/string-empty.cocci b/devel/coccinelle/string-empty.cocci deleted file mode 100644 index 16e6b1756a..0000000000 --- a/devel/coccinelle/string-empty.cocci +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020 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. - * - * Catch string comparisons where the pcmk__str_empty function could be used - * instead. Note that we are only catching uses involving identifiers (not - * expressions), but I think this is probably fine - we are likely not using - * the same expression multiple times in a single line of code. If some are - * found, it's easy enough to add another block here. - */ - -virtual internal - -@ string_empty depends on internal @ -type t; -identifier func !~ "pcmk__str_empty"; -char* I; -@@ -t func(...) { -... -( -- (I == NULL) || (strlen(I) == 0) -+ pcmk__str_empty(I) -| -- (I == NULL) || !strlen(I) -+ pcmk__str_empty(I) -| -- (I == NULL) || (I[0] == 0) -+ pcmk__str_empty(I) -| -- (I == NULL) || (*I == 0) -+ pcmk__str_empty(I) -| -- (I == NULL) || (I[0] == '\0') -+ pcmk__str_empty(I) -| -- (I == NULL) || (*I == '\0') -+ pcmk__str_empty(I) -) -... -} diff --git a/devel/coccinelle/string-null-matches.cocci b/devel/coccinelle/string-null-matches.cocci deleted file mode 100644 index b5471f694d..0000000000 --- a/devel/coccinelle/string-null-matches.cocci +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020 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. - * - * Catch places where a string can either be NULL or can match some other - * string. In these cases, passing the right flag to pcmk__str_eq will get - * the same result but without having to do the NULL comparison manually. - */ - -virtual internal - -@ string_null_matches_1 depends on internal @ -expression E1, E2; -@@ -- ((E1 == NULL) || crm_str_eq(E1, E2, TRUE)) -+ pcmk__str_eq(E1, E2, pcmk__str_null_matches) - -@ string_null_matches_2 depends on internal @ -expression E1, E2; -@@ -- ((E1 == NULL) || crm_str_eq(E2, E1, TRUE)) -+ pcmk__str_eq(E1, E2, pcmk__str_null_matches) - -@ string_null_matches_3 depends on internal @ -expression E1, E2; -@@ -- ((E1 == NULL) || safe_str_eq(E1, E2)) -+ pcmk__str_eq(E1, E2, pcmk__str_null_matches|pcmk__str_casei) - -@ string_null_matches_4 depends on internal @ -expression E1, E2; -@@ -- ((E1 == NULL) || safe_str_eq(E2, E1)) -+ pcmk__str_eq(E1, E2, pcmk__str_null_matches|pcmk__str_casei) - -@ string_null_matches_5 depends on internal @ -expression E1, E2; -@@ -- ((E1 == NULL) || strcmp(E1, E2) == 0) -+ pcmk__str_eq(E1, E2, pcmk__str_null_matches) - -@ string_null_matches_6 depends on internal @ -expression E1, E2; -@@ -- ((E1 == NULL) || strcmp(E2, E1) == 0) -+ pcmk__str_eq(E1, E2, pcmk__str_null_matches) - -@ string_null_matches_7 depends on internal @ -expression E1, E2; -@@ -- ((E1 == NULL) || !strcmp(E1, E2)) -+ pcmk__str_eq(E1, E2, pcmk__str_null_matches) - -@ string_null_matches_8 depends on internal @ -expression E1, E2; -@@ -- ((E1 == NULL) || !strcmp(E2, E1)) -+ pcmk__str_eq(E1, E2, pcmk__str_null_matches) - -@ string_null_matches_9 depends on internal @ -expression E1, E2; -@@ -- ((E1 != NULL) && strcmp(E1, E2) != 0) -+ !pcmk__str_eq(E1, E2, pcmk__str_null_matches) - -@ string_null_matches_10 depends on internal @ -expression E1, E2; -@@ -- ((E1 != NULL) && strcmp(E2, E1) != 0) -+ !pcmk__str_eq(E1, E2, pcmk__str_null_matches) diff --git a/devel/coccinelle/test/ref-passed-variables-inited.input.c b/devel/coccinelle/test/ref-passed-variables-inited.input.c deleted file mode 100644 index 9ddb626fd1..0000000000 --- a/devel/coccinelle/test/ref-passed-variables-inited.input.c +++ /dev/null @@ -1,88 +0,0 @@ -void foo(int *z) { - return: -} -void baz(int z) { - return; -} -void zob1(int **z) { - return; -} -void zob2(int ***z) { - return; -} - -void bar0(void) { - int i; - foo(&i); - baz(i); -} - -void bar1(void) { - int i; - foo(&i); - foo(&i); -} - -void bar2(void) { - int a = 1, b, c = 3; - foo(&a); - baz(a); - foo(&b); - baz(b); - foo(&c); - baz(c); -} - -void bar3(int *w) { - int i; - foo(&i); - *w = i; -} - -void bar4(int **w) { - int i; - foo(&i); - **w = *i; -} - -void bar5(int ***w) { - int *i; - zob(&i); - ***w = *i; -} - -void bar6(int ***w) { - int *i; - zob1(&i); - ***w = *i; -} - -void bar7(int ****w) { - int **i; - zob1(&i); - ****w = **i; -} - -int bar8(void) { - int i; - foo(&i); - return i; -} - -void not0(void) { - int i; - foo(&i); -} - -void not1(void) { - int i; - foo(&i); - i = 1; -} - -/* XXX false positive */ -void not2(void) { - int i; - foo(&i); - *(&i) = 2; -} diff --git a/devel/coccinelle/test/ref-passed-variables-inited.output b/devel/coccinelle/test/ref-passed-variables-inited.output deleted file mode 100644 index c7a9f91fed..0000000000 --- a/devel/coccinelle/test/ref-passed-variables-inited.output +++ /dev/null @@ -1,76 +0,0 @@ -@@ -12,19 +12,19 @@ void zob2(int ***z) { - } - - void bar0(void) { -- int i; -+ int i/*FIXME:initialize me*/; - foo(&i); - baz(i); - } - - void bar1(void) { -- int i; -+ int i/*FIXME:initialize me*/; - foo(&i); - foo(&i); - } - - void bar2(void) { -- int a = 1, b, c = 3; -+ int a = 1, b/*FIXME:initialize me*/, c = 3; - foo(&a); - baz(a); - foo(&b); -@@ -34,37 +34,37 @@ void bar2(void) { - } - - void bar3(int *w) { -- int i; -+ int i/*FIXME:initialize me*/; - foo(&i); - *w = i; - } - - void bar4(int **w) { -- int i; -+ int i/*FIXME:initialize me*/; - foo(&i); - **w = *i; - } - - void bar5(int ***w) { -- int *i; -+ int *i/*FIXME:initialize me*/; - zob(&i); - ***w = *i; - } - - void bar6(int ***w) { -- int *i; -+ int *i/*FIXME:initialize me*/; - zob1(&i); - ***w = *i; - } - - void bar7(int ****w) { -- int **i; -+ int **i/*FIXME:initialize me*/; - zob1(&i); - ****w = **i; - } - - int bar8(void) { -- int i; -+ int i/*FIXME:initialize me*/; - foo(&i); - return i; - } -@@ -82,7 +82,7 @@ void not1(void) { - - /* XXX false positive */ - void not2(void) { -- int i; -+ int i/*FIXME:initialize me*/; - foo(&i); - *(&i) = 2; - } diff --git a/devel/coccinelle/test/testrunner.sh b/devel/coccinelle/test/testrunner.sh deleted file mode 100755 index 07e47952de..0000000000 --- a/devel/coccinelle/test/testrunner.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -set -eu -_tmpdir=$(mktemp -d /tmp/coccicheck-XXXXXX) -_bname=$(basename "$1" .input.c) -_dname=$(dirname "$1") -spatch --very-quiet --sp-file "${_dname}/../${_bname}.cocci" "$1" \ - | tail -n+3 > "${_tmpdir}/out" -diff -u "${_dname}/${_bname}.output" "${_tmpdir}/out" - -if [ -d "${_tmpdir}" ]; then - rm "${_tmpdir}"/* - rmdir "${_tmpdir}" -fi diff --git a/devel/coccinelle/use-func.cocci b/devel/coccinelle/use-func.cocci deleted file mode 100644 index bfca91985e..0000000000 --- a/devel/coccinelle/use-func.cocci +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2020 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. - */ - -/* - * Always use __func__ (which is in the C99 standard) instead of __FUNCTION__ - * (which is an older GNU C extension) - */ - -virtual internal - -@ use_func @ -@@ -( -- __FUNCTION__ -+ __func__ -) diff --git a/doc/sphinx/Pacemaker_Development/helpers.rst b/doc/sphinx/Pacemaker_Development/helpers.rst index 5a7da9bae0..53cf035d62 100644 --- a/doc/sphinx/Pacemaker_Development/helpers.rst +++ b/doc/sphinx/Pacemaker_Development/helpers.rst @@ -1,642 +1,579 @@ 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 ``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 the following includes are at the top of ``Makefile.am``: .. code-block:: none include $(top_srcdir)/mk/common.mk include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk * If necessary, settings from those includes can be overridden like so: .. code-block:: none AM_TESTS_ENVIRONMENT += PCMK_CTS_CLI_DIR=$(top_srcdir)/cts/cli AM_CPPFLAGS += -I$(top_srcdir) LDADD += $(top_builddir)/lib/pengine/libpe_status_test.la * 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 */ PCMK__UNIT_TEST(NULL, NULL, /* Register your test functions here */) 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 ``PCMK__UNIT_TEST`` macro: .. code-block:: c PCMK__UNIT_TEST(NULL, NULL, cmocka_unit_test(regex)) Most unit tests do not require a setup and teardown function to be executed around the entire group of tests. On occassion, this may be necessary. Simply pass those functions in as the first two parameters to ``PCMK__UNIT_TEST`` instead of using NULL. Assertions __________ In addition to the `assertions provided by cmocka `_, ``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 ``pcmk__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, ``pcmk__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. Fuzz Testing ############ Pacemaker is integrated with the `OSS-Fuzz `_ project. OSS-Fuzz calls selected Pacemaker APIs with random argument values to catch edge cases that might be missed by other forms of testing. The OSS-Fuzz project has a contact address for Pacemaker in projects/pacemaker/project.yaml that will receive bug reports. The address must have been used to commit to Pacemaker, and should be tied to a Google account. Open reports that aren't security-related can be seen at `OSS-Fuzz testcases `_. Fuzzers _______ Each fuzz-tested library has a fuzzers subdirectory (for example, ``lib/common/fuzzers``). That directory has a file for each fuzzed source file, named the same except ending in ``_fuzzer.c`` (for example, ``lib/common/fuzzers/strings_fuzzer.c`` has fuzzing for ``lib/common/strings.c``). Those files are not built or distributed as part of Pacemaker but are used by OSS-Fuzz (see ``projects/pacemaker/build.sh`` in the OSS-Fuzz repository). By default, fuzzing uses `libFuzzer `_. Only Pacemaker APIs that accept any input and do not exit can be fuzzed. Ideally, fuzzed functions will not modify global state or vary code paths by anything other than the fuzzed input (such as environment variable values, date/time, etc.). Local Fuzzing _____________ You can use OSS-Fuzz locally to run fuzz testing or reproduce issues reported by OSS-Fuzz. To prep a test host: 1. If podman is installed, it will conflict with Docker, so remove it first. Example for RHEL-like OSes: * ``dnf remove runc`` 1. Install and start Docker. Example for RHEL-like OSes: * ``dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo`` * ``dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin`` * ``usermod -a -G docker $USER`` 2. Clone the OSS-Fuzz repository: * ``cd`` to wherever you want to put it * ``git clone https://github.com/google/oss-fuzz.git`` * ``cd oss-fuzz`` 3. Specify the Pacemaker source you want to test: * Edit ``projects/pacemaker/Dockerfile`` and replace the last ``git clone`` with the source that you want to test. For example, if you have a branch ``my-fuzzing-branch`` that you've pushed to your GitHub account, you could use: ``git clone -b my-fuzzing-branch --single-branch --depth 1 https://github.com/$USER/pacemaker``. To fuzz the code: 1. Ensure Docker is running: * ``systemctl start docker`` 2. Build the necessary Docker containers: * ``python3 infra/helper.py build_image pacemaker`` 3. Build the fuzzers. Choose a sanitizer (for example, ``SANITIZER=address``). There are three possible sanitizers: address, memory, and undefined. The memory sanitizer requires special preparation and is generally not used. If you are reproducing an OSS-Fuzz-reported issue, the issue will list the sanitizer that was used. * ``python3 infra/helper.py build_fuzzers --sanitizer $SANITIZER pacemaker`` 4. Ensure the build succeeded (use the same sanitizer as the previous step): * ``python3 infra/helper.py check_build --sanitizer $SANITIZER pacemaker`` 5. If you want to run fuzzing yourself, choose a fuzzer (for example, ``FUZZER=iso8601_fuzzer``). Create a temporary directory for the fuzzer's outputs, then run the fuzzing command, which will fuzz for 25 seconds then time out: * ``rm -rf /tmp/corpus >/dev/null 2>&/dev/null`` * ``mkdir /tmp/corpus`` * ``python3 infra/helper.py run_fuzzer --corpus-dir=/tmp/corpus pacemaker $FUZZER`` * This can be repeated with different fuzzers. The ``build_fuzzers`` step can also be repeated with a different sanitizer, and the fuzzers tested again. 6. If you want to reproduce an OSS-Fuzz-reported issue, make a note of the fuzzer that was used (``$FUZZER`` in this example) and download the provided reproducer test case file (``$TESTCASE`` in this example), then run: * ``python3 infra/helper.py reproduce pacemaker $FUZZER $TESTCASE`` For details, see the `OSS-Fuzz documentation `_. Code Coverage ############# Figuring out what needs unit tests written is the purpose of a code coverage tool. The Pacemaker build process uses ``lcov`` and special make targets to generate an HTML coverage report that can be inspected with any web browser. To start, you'll need to install the ``lcov`` package which is included in most distributions. Next, reconfigure the source tree: .. code-block:: none $ ./configure --with-coverage Then run ``make -C devel coverage``. This will do the same thing as ``make check``, but will generate a bunch of intermediate files as part of the compiler's output. Essentially, the coverage tools run all the unit tests and make a note if a given line if code is executed as a part of some test program. This will include not just things run as part of the tests but anything in the setup and teardown functions as well. Afterwards, the HTML report will be in ``coverage/index.html``. You can drill down into individual source files to see exactly which lines are covered and which are not, which makes it easy to target new unit tests. Note that sometimes, it is impossible to achieve 100% coverage for a source file. For instance, how do you test a function with a return type of void that simply returns on some condition? Note that Pacemaker's overall code coverage numbers are very low at the moment. One reason for this is the large amount of code in the ``daemons`` directory that will be very difficult to write unit tests for. For now, it is best to focus efforts on increasing the coverage on individual libraries. Additionally, there is a ``coverage-cts`` target that does the same thing but instead of testing ``make check``, it tests ``cts/cts-cli``. The idea behind this target is to see what parts of our command line tools are covered by our regression tests. It is probably best to clean and rebuild the source tree when switching between these various targets. 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.