build REFACTOR separate code coverage into a module
diff --git a/CMakeModules/GenCoverage.cmake b/CMakeModules/GenCoverage.cmake
new file mode 100644
index 0000000..43a576b
--- /dev/null
+++ b/CMakeModules/GenCoverage.cmake
@@ -0,0 +1,104 @@
+# 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()
+
+        # 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()