feat(tests): Add support for coverage report

This patch enables support for coverage report on the
unit tests by using the gcovr tool.

The coverage report is stored in ${RMM_BUILD_DIR}/$<CONFIG>/coverage

Signed-off-by: Javier Almansa Sobrino <javier.almansasobrino@arm.com>
Change-Id: I5b6a2f3ebf13cbc036c0d64f422ea18f337dd8f5
diff --git a/cmake/CoverageReport.cmake b/cmake/CoverageReport.cmake
new file mode 100644
index 0000000..67c901d
--- /dev/null
+++ b/cmake/CoverageReport.cmake
@@ -0,0 +1,151 @@
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+#
+
+arm_config_option(
+    NAME RMM_COVERAGE
+    HELP "Enable coverage tests"
+    TYPE BOOL
+    DEFAULT "OFF")
+
+arm_config_option(
+    NAME RMM_HTML_COV_REPORT
+    HELP "Enable html coverage report"
+    TYPE BOOL
+    DEPENDS RMM_COVERAGE
+    DEFAULT "ON")
+
+arm_config_option(
+    NAME COVERAGE_REPORT_NAME
+    HELP "Canonical name for the coverage report"
+    TYPE STRING
+    DEPENDS RMM_COVERAGE
+    DEFAULT "tf-rmm-coverage"
+    ADVANCED)
+
+macro(check_and_prepare_c_coverage_flags)
+  # Store a copy of CMAKE_C_FLAGS
+  set(CMAKE_C_FLAGS_BACKUP "${CMAKE_C_FLAGS}")
+
+  foreach(flag ${ARGN})
+    string(REPLACE "-" "_" flag_no_hyphen ${flag})
+    check_c_compiler_flag("-${flag}" COVERAGE_C_FLAG_${flag_no_hyphen})
+    if (COVERAGE_C_FLAG_${flag_no_hyphen})
+      # Some of the coverage flags depend on the previous ones being
+      # enabled, so add them to the C Flags now for the next check.
+      string(APPEND CMAKE_C_FLAGS " -${flag}")
+      string(APPEND COVERAGE_C_FLAGS " -${flag}")
+    else()
+      set(COVERAGE_SUPPORTED "FALSE")
+    endif()
+  endforeach()
+
+  # Restore CMAKE_C_FLAGS
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS_BACKUP}")
+endmacro(check_and_prepare_c_coverage_flags)
+
+macro(check_and_prepare_cxx_coverage_flags)
+  # Store a copy of CMAKE_CXX_FLAGS
+  set(CMAKE_CXX_FLAGS_BACKUP "${CMAKE_CXX_FLAGS}")
+
+  foreach(flag ${ARGN})
+    string(REPLACE "-" "_" flag_no_hyphen ${flag})
+    check_cxx_compiler_flag("-${flag}" COVERAGE_CXX_FLAG_${flag_no_hyphen})
+    if (COVERAGE_CXX_FLAG_${flag_no_hyphen})
+      # Some of the coverage flags depend on the previous ones being
+      # enabled, so add them to the CXX Flags now for the next check.
+      string(APPEND CMAKE_CXX_FLAGS " -${flag}")
+      string(APPEND COVERAGE_CXX_FLAGS " -${flag}")
+    endif()
+  endforeach()
+
+  # Restore CMAKE_CXX_FLAGS
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_BACKUP}")
+endmacro(check_and_prepare_cxx_coverage_flags)
+
+if(RMM_COVERAGE)
+
+  find_program(GCOVR_EXECUTABLE "gcovr" DOC "Path to gcovr")
+
+  if(${GCOVR_EXECUTABLE} STREQUAL "GCOVR_EXECUTABLE-NOTFOUND")
+    message (WARNING "gcovr executable not found. Coverage tests disabled")
+    return()
+  endif()
+
+  include(CheckCCompilerFlag)
+  include(CheckCXXCompilerFlag)
+
+  if(CMAKE_C_COMPILER_ID STREQUAL "Clang")
+    # Using LLVM. Select the coverage flags to test
+    set(COVERAGE_FLAGS
+        ftest-coverage
+        fprofile-instr-generate
+        fprofile-arcs
+        fcoverage-mapping)
+
+    # Setup the right coverage tool if using llvm
+    set(GCOVR_EXE_OPTION --gcov-executable "llvm-cov gcov"
+        CACHE INTERNAL "GCOV_EXECUTABLE")
+
+  elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU")
+    # Using GNU. Select the coverage flags to test
+    set(COVERAGE_FLAGS
+        -coverage)
+
+    # Flags needed to enable coverage testing
+    string(APPEND CMAKE_EXE_LINKER_FLAGS " -coverage -lgcov ")
+  else()
+    message(WARNING "Toolchain ${CMAKE_C_COMPILER_ID} does not support coverage")
+    return()
+  endif()
+
+  set(COVERAGE_SUPPORTED "TRUE")
+  check_and_prepare_c_coverage_flags(${COVERAGE_FLAGS})
+  check_and_prepare_cxx_coverage_flags(${COVERAGE_FLAGS})
+
+  # If coverage is not supported by the C compiler, or the C and C++
+  # compilers do not support the same set of coverage flags (which can
+  # lead to link problems), abort.
+  if ((COVERAGE_SUPPORTED STREQUAL "FALSE") OR
+      (NOT (COVERAGE_C_FLAGS STREQUAL COVERAGE_CXX_FLAGS)))
+    message (WARNING "Toolchain ${CMAKE_C_COMPILER_ID} does not support coverage")
+    return()
+  endif()
+
+  # Setup flags for coverage
+  foreach(language in ITEMS C CXX)
+    string(APPEND CMAKE_${language}_FLAGS
+           " ${COVERAGE_${language}_FLAGS} ")
+  endforeach()
+
+  # Directory where to store the results
+  set(COVERAGE_DIRECTORY
+      "${CMAKE_BINARY_DIR}/$<CONFIG>/coverage")
+
+  set(COVERAGE_OUTPUT "${COVERAGE_DIRECTORY}/${COVERAGE_REPORT_NAME}")
+
+  if(RMM_HTML_COV_REPORT)
+      set(HTML_REPORT --html-details ${COVERAGE_OUTPUT}.html)
+  endif()
+
+  #
+  # Rules for coverage report generation
+  #
+  add_custom_target(run-coverage
+                    COMMAND ${CMAKE_COMMAND} -E make_directory "${COVERAGE_DIRECTORY}"
+                    COMMAND ${GCOVR_EXECUTABLE}
+                            ${GCOVR_EXE_OPTION}
+                            --exclude "'((.+)ext(.+))|((.+)CMakeFiles(.+)\..)|((.+)\.cpp)|((.+)test(.+))'"
+                            -r ${CMAKE_SOURCE_DIR}
+                            -x ${COVERAGE_OUTPUT}.xml
+                            ${HTML_REPORT}
+                            ${CMAKE_BINARY_DIR})
+  #
+  # Add dependency on unit test target if being invoked in a
+  # multi-target build command line.
+  #
+  if(RMM_UNITTESTS)
+    add_dependencies(run-coverage run-unittests)
+  endif()
+endif() # RMM_COVERAGE