build REFACTOR main cmake list organized into sections
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f98a0fe..68c5e29 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,7 +42,7 @@
             build-type: "Release",
             dep-build-type: "Release",
             cc: "gcc",
-            options: "-DENABLE_BUILD_TESTS=ON -DENABLE_DNSSEC=ON",
+            options: "-DENABLE_TESTS=ON -DENABLE_DNSSEC=ON",
             packages: "",
             snaps: "",
             make-prepend: "",
@@ -54,7 +54,7 @@
             build-type: "Release",
             dep-build-type: "Release",
             cc: "clang",
-            options: "-DENABLE_BUILD_TESTS=ON -DENABLE_DNSSEC=ON",
+            options: "-DENABLE_TESTS=ON -DENABLE_DNSSEC=ON",
             packages: "",
             snaps: "",
             make-prepend: "",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 39fcbcb..d611138 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,6 +13,7 @@
 include(ABICheck)
 include(SourceFormat)
 include(GenDoc)
+include(GenCoverage)
 
 if(POLICY CMP0075)
     cmake_policy(SET CMP0075 NEW)
@@ -69,51 +70,25 @@
 # libyang SO version required
 set(LIBYANG_DEP_SOVERSION_MAJOR 2)
 
-# build options
-if(("${BUILD_TYPE_UPPER}" STREQUAL "DEBUG") OR ("${BUILD_TYPE_UPPER}" STREQUAL "RELWITHDEBINFO"))
-    option(ENABLE_BUILD_TESTS "Build tests" ON)
-    option(ENABLE_VALGRIND_TESTS "Build tests with valgrind" ON)
-else()
-    option(ENABLE_BUILD_TESTS "Build tests" OFF)
-    option(ENABLE_VALGRIND_TESTS "Build tests with valgrind" OFF)
-endif()
-option(ENABLE_COVERAGE "Build code coverage report from tests" OFF)
-
-if(ENABLE_COVERAGE)
-    find_program(PATH_GCOV NAMES gcov)
-    if(NOT PATH_GCOV)
-        message(WARNING "'gcov' executable not found! Disabling building code coverage report.")
-        set(ENABLE_COVERAGE OFF)
-    endif()
-
-    find_program(PATH_LCOV NAMES lcov)
-    if(NOT PATH_LCOV)
-        message(WARNING "'lcov' executable not found! Disabling building code coverage report.")
-        set(ENABLE_COVERAGE OFF)
-    endif()
-
-    find_program(PATH_GENHTML NAMES genhtml)
-    if(NOT PATH_GENHTML)
-        message(WARNING "'genhtml' executable not found! Disabling building code coverage report.")
-        set(ENABLE_COVERAGE OFF)
-    endif()
-
-    if(NOT CMAKE_COMPILER_IS_GNUCC)
-        message(WARNING "Compiler is not gcc! Coverage may break the tests!")
-    endif()
-
-    if(ENABLE_COVERAGE)
-        set(CMAKE_C_FLAGS_COVERAGE "--coverage -fprofile-arcs -ftest-coverage")
-    endif()
-endif()
-
+#
 # compilation flags
+#
 set(CMAKE_C_FLAGS           "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_COVERAGE} -Wall -Wextra -fvisibility=hidden -std=gnu99")
 set(CMAKE_C_FLAGS_RELEASE   "-DNDEBUG -O2")
 set(CMAKE_C_FLAGS_DEBUG     "-g -O0")
 set(CMAKE_C_FLAGS_ABICHECK  "-g -Og")
 
+#
 # options
+#
+if(("${BUILD_TYPE_UPPER}" STREQUAL "DEBUG") OR ("${BUILD_TYPE_UPPER}" STREQUAL "RELWITHDEBINFO"))
+    option(ENABLE_TESTS "Build tests" ON)
+    option(ENABLE_VALGRIND_TESTS "Build tests with valgrind" ON)
+else()
+    option(ENABLE_TESTS "Build tests" OFF)
+    option(ENABLE_VALGRIND_TESTS "Build tests with valgrind" OFF)
+endif()
+option(ENABLE_COVERAGE "Build code coverage report from tests" OFF)
 option(ENABLE_SSH "Enable NETCONF over SSH support (via libssh)" ON)
 option(ENABLE_TLS "Enable NETCONF over TLS support (via OpenSSL)" ON)
 option(ENABLE_DNSSEC "Enable support for SSHFP retrieval using DNSSEC for SSH (requires OpenSSL and libval)" OFF)
@@ -123,12 +98,9 @@
 set(TIMEOUT_STEP 100 CACHE STRING "Number of microseconds tasks are repeated until timeout elapses")
 set(YANG_MODULE_DIR "${CMAKE_INSTALL_PREFIX}/share/yang/modules" CACHE STRING "Directory with common YANG modules")
 
-if(ENABLE_DNSSEC AND NOT ENABLE_SSH)
-    message(WARNING "DNSSEC SSHFP retrieval cannot be used without SSH support.")
-    set(ENABLE_DNSSEC OFF)
-endif()
-
-# source files
+#
+# sources
+#
 set(libsrc
     src/io.c
     src/log.c
@@ -185,11 +157,49 @@
     tests/*.c
     tests/client/*.c)
 
+#
+# checks
+#
+if(ENABLE_DNSSEC AND NOT ENABLE_SSH)
+    message(WARNING "DNSSEC SSHFP retrieval cannot be used without SSH support.")
+    set(ENABLE_DNSSEC OFF)
+endif()
+
+if(ENABLE_VALGRIND_TESTS)
+    find_program(VALGRIND_FOUND valgrind)
+    if(NOT VALGRIND_FOUND)
+        message(WARNING "valgrind executable not found! Disabling memory leaks tests.")
+        set(ENABLE_VALGRIND_TESTS OFF)
+    else()
+        set(ENABLE_TESTS ON)
+    endif()
+endif()
+
+if(ENABLE_TESTS)
+    find_package(CMocka 1.0.0)
+    if(NOT CMOCKA_FOUND)
+        message(STATUS "Disabling tests because of missing CMocka")
+        set(ENABLE_TESTS OFF)
+    endif()
+endif()
+
+if(ENABLE_COVERAGE)
+    gen_coverage_enable(${ENABLE_TESTS})
+endif()
+
+if ("${BUILD_TYPE_UPPER}" STREQUAL "DEBUG")
+    source_format_enable()
+endif()
+
 if("${BUILD_TYPE_UPPER}" STREQUAL "DOCONLY")
     gen_doc("${doxy_files}" ${LIBNETCONF2_VERSION} ${LIBNETCONF2_DESCRIPTION} "")
     return()
 endif()
 
+#
+# targets
+#
+
 # use compat
 use_compat()
 
@@ -300,25 +310,14 @@
     endif()
 endif()
 
-if ("${BUILD_TYPE_UPPER}" STREQUAL "DEBUG")
-    # enable before adding tests to let them detect that format checking is available - one of the tests is format checking
-    source_format_enable()
+# tests
+if(ENABLE_TESTS)
+    enable_testing()
+    add_subdirectory(tests)
 endif()
 
-# source files to be covered by the 'format' target and a test with 'format-check' target
-source_format(${format_sources})
-
-if(ENABLE_VALGRIND_TESTS)
-    set(ENABLE_BUILD_TESTS ON)
-endif()
-
-if(ENABLE_BUILD_TESTS)
-    find_package(CMocka 1.0.0)
-    if(CMOCKA_FOUND)
-        enable_testing()
-        add_subdirectory(tests)
-    endif()
-endif()
+# create coverage target for generating coverage reports
+gen_coverage("test_.*" "test_.*_valgrind")
 
 # generate doxygen documentation for libnetconf2 API
 gen_doc("${doxy_files}" ${LIBNETCONF2_VERSION} ${LIBNETCONF2_DESCRIPTION} "")
@@ -328,6 +327,9 @@
     lib_abi_check(netconf2 "${headers}" ${LIBNETCONF2_SOVERSION_FULL} e7402149e5b36de7acab2e38970a3a9d6a8165d5)
 endif()
 
+# source files to be covered by the 'format' target and a test with 'format-check' target
+source_format(${format_sources})
+
 # clean cmake cache
 add_custom_target(cleancache
                   COMMAND make clean
diff --git a/CMakeModules/GenCoverage.cmake b/CMakeModules/GenCoverage.cmake
new file mode 100644
index 0000000..55be637
--- /dev/null
+++ b/CMakeModules/GenCoverage.cmake
@@ -0,0 +1,118 @@
+# generate test code coverage report
+
+# check that coverage tools are available - always use before GEN_COVERAGE
+macro(GEN_COVERAGE_ENABLE ENABLE_TESTS)
+    # make into normal variable
+    set(TESTS_ENABLED ${ENABLE_TESTS})
+
+    set(GEN_COVERAGE_ENABLED ON)
+    if(NOT TESTS_ENABLED)
+        message(WARNING "You cannot generate coverage when tests are disabled. Enable test by additing parameter -DENABLE_BUILD_TESTS=ON or run cmake with Debug build target.")
+        set(GEN_COVERAGE_ENABLED OFF)
+    endif()
+
+    if(GEN_COVERAGE_ENABLED)
+        find_program(PATH_GCOV NAMES gcov)
+        if(NOT PATH_GCOV)
+            message(WARNING "gcov executable not found! Disabling building code coverage report.")
+            set(GEN_COVERAGE_ENABLED OFF)
+        endif()
+    endif()
+
+    if(GEN_COVERAGE_ENABLED)
+        find_program(PATH_LCOV NAMES lcov)
+        if(NOT PATH_LCOV)
+            message(WARNING "lcov executable not found! Disabling building code coverage report.")
+            set(GEN_COVERAGE_ENABLED OFF)
+        endif()
+    endif()
+
+    if(GEN_COVERAGE_ENABLED)
+        find_program(PATH_GENHTML NAMES genhtml)
+        if(NOT PATH_GENHTML)
+            message(WARNING "genhtml executable not found! Disabling building code coverage report.")
+            set(GEN_COVERAGE_ENABLED OFF)
+        endif()
+    endif()
+
+    if(GEN_COVERAGE_ENABLED)
+        if(NOT CMAKE_COMPILER_IS_GNUCC)
+            message(WARNING "Compiler is not gcc! Coverage may break the tests!")
+        endif()
+
+        execute_process(
+            COMMAND bash "-c" "${CMAKE_C_COMPILER} --version | head -n1 | sed \"s/.* (.*) \\([0-9]\\+.[0-9]\\+.[0-9]\\+ .*\\)/\\1/\""
+            OUTPUT_VARIABLE GCC_VERSION_FULL
+            OUTPUT_STRIP_TRAILING_WHITESPACE
+        )
+        execute_process(
+            COMMAND bash "-c" "${PATH_GCOV} --version | head -n1 | sed \"s/.* (.*) \\([0-9]\\+.[0-9]\\+.[0-9]\\+ .*\\)/\\1/\""
+            OUTPUT_VARIABLE GCOV_VERSION_FULL
+            OUTPUT_STRIP_TRAILING_WHITESPACE
+        )
+        if(NOT GCC_VERSION_FULL STREQUAL GCOV_VERSION_FULL)
+            message(WARNING "gcc and gcov versions do not match! Generating coverage may fail with errors.")
+        endif()
+
+        # add specific required compile flags
+        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage -fprofile-arcs -ftest-coverage")
+    endif()
+endmacro()
+
+# tests are always expected to be in ${CMAKE_SOURCE_DIR}/tests
+function(GEN_COVERAGE MATCH_TEST_REGEX EXCLUDE_TEST_REGEX)
+    if(NOT GEN_COVERAGE_ENABLED)
+        return()
+    endif()
+
+    # destination
+    set(COVERAGE_DIR        "${CMAKE_BINARY_DIR}/code_coverage/")
+    set(COVERAGE_FILE_RAW   "${CMAKE_BINARY_DIR}/coverage_raw.info")
+    set(COVERAGE_FILE_CLEAN "${CMAKE_BINARY_DIR}/coverage_clean.info")
+
+    # test match/exclude
+    if(MATCH_TEST_REGEX)
+        set(MATCH_TEST_ARGS -R \"${MATCH_TEST_REGEX}\")
+    endif()
+    if(EXCLUDE_TEST_REGEX)
+        set(EXCLUDE_TEST_ARGS -E \"${EXCLUDE_TEST_REGEX}\")
+    endif()
+
+    # coverage target
+    add_custom_target(coverage
+        COMMENT "Generating code coverage..."
+        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
+        # Cleanup code counters
+        COMMAND "${PATH_LCOV}" --directory . --zerocounters --quiet
+
+        # Run tests
+        COMMAND "${CMAKE_CTEST_COMMAND}" --quiet ${MATCH_TEST_ARGS} ${EXCLUDE_TEST_ARGS}
+
+        # Capture the counters
+        COMMAND "${PATH_LCOV}"
+            --directory .
+            --rc lcov_branch_coverage=1
+            --rc 'lcov_excl_line=assert'
+            --capture --quiet
+            --output-file "${COVERAGE_FILE_RAW}"
+        # Remove coverage of tests, system headers, etc.
+        COMMAND "${PATH_LCOV}"
+            --remove "${COVERAGE_FILE_RAW}" '${CMAKE_SOURCE_DIR}/tests/*'
+            --rc lcov_branch_coverage=1
+            --quiet --output-file "${COVERAGE_FILE_CLEAN}"
+        # Generate HTML report
+        COMMAND "${PATH_GENHTML}"
+            --branch-coverage --function-coverage --quiet --title "${PROJECT_NAME}"
+            --legend --show-details --output-directory "${COVERAGE_DIR}"
+            "${COVERAGE_FILE_CLEAN}"
+        # Delete the counters
+        COMMAND "${CMAKE_COMMAND}" -E remove
+            ${COVERAGE_FILE_RAW} ${COVERAGE_FILE_CLEAN}
+    )
+
+    add_custom_command(TARGET coverage POST_BUILD
+        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/tests"
+        COMMENT "To see the code coverage report, open ${COVERAGE_DIR}index.html"
+        COMMAND ;
+    )
+endfunction()
diff --git a/README.md b/README.md
index b4a180b..9faab5f 100644
--- a/README.md
+++ b/README.md
@@ -224,6 +224,7 @@
 ```
 and then the make's `coverage` target should be available to generate statistics:
 ```
+$ make
 $ make coverage
 ```
 
@@ -252,7 +253,7 @@
 In case of the `Release` mode, the tests are not built by default (it requires
 additional dependency), but it can be enabled via cmake option:
 ```
-$ cmake -DENABLE_BUILD_TESTS=ON ..
+$ cmake -DENABLE_TESTS=ON ..
 ```
 
 Note that if the necessary [cmocka](https://cmocka.org/) headers are not present
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a3c5fbd..f9b9f3c 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -63,65 +63,15 @@
 endforeach()
 
 if(ENABLE_VALGRIND_TESTS)
-    find_program(valgrind_FOUND valgrind)
-    if(valgrind_FOUND)
-        foreach(test_name IN LISTS tests)
-            add_test(${test_name}_valgrind valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1
-                --suppressions=${PROJECT_SOURCE_DIR}/tests/ld.supp ${CMAKE_BINARY_DIR}/tests/${test_name})
-        endforeach()
+    foreach(test_name IN LISTS tests)
+        add_test(${test_name}_valgrind valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1
+            --suppressions=${PROJECT_SOURCE_DIR}/tests/ld.supp ${CMAKE_BINARY_DIR}/tests/${test_name})
+    endforeach()
 
-        foreach(test_name IN LISTS client_tests)
-            add_test(${test_name}_valgrind valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1
-                --suppressions=${PROJECT_SOURCE_DIR}/tests/ld.supp ${CMAKE_BINARY_DIR}/tests/${test_name})
-        endforeach()
-    else()
-        message("-- valgrind executable not found! Disabling memory leaks tests")
-    endif()
-endif()
-
-if(ENABLE_COVERAGE)
-    # Destination
-    set(COVERAGE_DIR        "${CMAKE_BINARY_DIR}/tests/code_coverage/")
-    set(COVERAGE_FILE_RAW   "${CMAKE_BINARY_DIR}/tests/coverage_raw.info")
-    set(COVERAGE_FILE_CLEAN "${CMAKE_BINARY_DIR}/tests/coverage_clean.info")
-
-    # Add coverage target
-    add_custom_target(coverage
-        COMMENT "Generating code coverage..."
-        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
-        # Cleanup code counters
-        COMMAND "${PATH_LCOV}" --directory . --zerocounters --quiet
-
-        # Run tests
-        COMMAND "${CMAKE_CTEST_COMMAND}" --quiet
-
-        # Capture the counters
-        COMMAND "${PATH_LCOV}"
-            --directory .
-            --rc lcov_branch_coverage=1
-            --rc 'lcov_excl_line=assert'
-            --capture --quiet
-            --output-file "${COVERAGE_FILE_RAW}"
-        # Remove coverage of tests, system headers, etc.
-        COMMAND "${PATH_LCOV}"
-            --remove "${COVERAGE_FILE_RAW}" '${CMAKE_SOURCE_DIR}/tests/*'
-            --rc lcov_branch_coverage=1
-            --quiet --output-file "${COVERAGE_FILE_CLEAN}"
-        # Generate HTML report
-        COMMAND "${PATH_GENHTML}"
-            --branch-coverage --function-coverage --quiet --title "libnetconf2"
-            --legend --show-details --output-directory "${COVERAGE_DIR}"
-            "${COVERAGE_FILE_CLEAN}"
-        # Delete the counters
-        COMMAND "${CMAKE_COMMAND}" -E remove
-            ${COVERAGE_FILE_RAW} ${COVERAGE_FILE_CLEAN}
-        )
-
-    add_custom_command(TARGET coverage POST_BUILD
-        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/tests"
-        COMMENT "To see the code coverage report, open ${COVERAGE_DIR}index.html"
-        COMMAND ;
-        )
+    foreach(test_name IN LISTS client_tests)
+        add_test(${test_name}_valgrind valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1
+            --suppressions=${PROJECT_SOURCE_DIR}/tests/ld.supp ${CMAKE_BINARY_DIR}/tests/${test_name})
+    endforeach()
 endif()
 
 include_directories(${CMAKE_SOURCE_DIR}/src ${PROJECT_BINARY_DIR})