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/CMakeLists.txt b/CMakeLists.txt
index a793590..074b80c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -61,12 +61,18 @@
 if(RMM_STATIC_ANALYSIS_CPPCHECK)
     set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 endif()
+
 #
 # Include the platform makefile
 #
 include("cmake/Platforms.cmake")
 
 #
+# Include Coverage report framework
+#
+include("cmake/CoverageReport.cmake")
+
+#
 # Include the Unit Test Framework
 #
 include(UnitTestFramework)
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
diff --git a/docs/getting_started/build-options.rst b/docs/getting_started/build-options.rst
index d41a145..0db4cdc 100644
--- a/docs/getting_started/build-options.rst
+++ b/docs/getting_started/build-options.rst
@@ -162,7 +162,7 @@
     cmake -DRMM_CONFIG=fvp_defcfg -S ${RMM_SOURCE_DIR} -B ${RMM_BUILD_DIR}
     cmake --build ${RMM_BUILD_DIR} -- checkincludes-codebase
 
-14.  Perform unit tests on development host:
+14. Perform unit tests on development host:
 
 Build and run unit tests on host platform. It is recommended to do the Debug
 build of RMM.
@@ -172,6 +172,17 @@
     cmake -DRMM_CONFIG=host_defcfg -DHOST_VARIANT=host_test -DCMAKE_BUILD_TYPE=Debug -S ${RMM_SOURCE_DIR} -B ${RMM_BUILD_DIR}
     cmake --build ${RMM_BUILD_DIR} -- run-unittests
 
+Run coverage analysis on unit tests.
+
+.. code-block:: bash
+
+    cmake -DRMM_CONFIG=host_defcfg -DHOST_VARIANT=host_test -DRMM_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug -S ${RMM_SOURCE_DIR} -B ${RMM_BUILD_DIR}
+    cmake --build ${RMM_BUILD_DIR} -- run-coverage
+
+The above commands will automatically generate the HTML coverage report in folder
+`build/Debug/coverage` within build directory. The HTML generation can be
+disabled by setting `RMM_HTML_COV_REPORT=OFF`.
+
 .. _build_options_table:
 
 ###################
@@ -208,9 +219,9 @@
    MBEDTLS_ECP_MAX_OPS		,248 -			,1000			,"Number of max operations per ECC signing iteration"
    RMM_FPU_USE_AT_REL2		,ON | OFF		,OFF(fake_host) ON(aarch64),"Enable FPU/SIMD usage in RMM."
    RMM_MAX_GRANULES		,			,0			,"Maximum number of memory granules available to the system"
-   HOST_VARIANT			,host_build | host_test	,host_build		, "Variant to build for the host platform. Only available when RMM_PLATFORM=host"
-
-
+   HOST_VARIANT			,host_build | host_test	,host_build		,"Variant to build for the host platform. Only available when RMM_PLATFORM=host"
+   RMM_COVERAGE 		,ON | OFF		,OFF			,"Enable coverage analysis"
+   RMM_HTML_COV_REPORT		,ON | OFF		,ON			,"Enable HTML output report for coverage analysis"
 
 .. _llvm_build:
 
diff --git a/docs/getting_started/getting-started.rst b/docs/getting_started/getting-started.rst
index 4e8e07b..88747c9 100644
--- a/docs/getting_started/getting-started.rst
+++ b/docs/getting_started/getting-started.rst
@@ -49,6 +49,7 @@
    "Git",, "Firmware, Documentation"
    "Graphviz dot",">v2.38.0","Documentation"
    "docutils",">v2.38.0","Documentation"
+   "gcovr",">=v4.2","Tools(Coverage analysis)"
 
 .. _getting_started_toolchain:
 
@@ -143,6 +144,30 @@
     cd <rmm source folder>
     pip3 install -r docs/requirements.txt
 
+############################################
+Install coverage tools analysis dependencies
+############################################
+
+.. note::
+
+    This is an optional step only needed if you intend to run coverage
+    analysis on the source code.
+
+On Ubuntu, ``gcovr`` tool can be installed in two different ways:
+
+Using the pagckage manager:
+
+.. code-block:: bash
+
+    sudo apt-get install gcovr
+
+The second (and recommended) way is install it with ``pip3``:
+
+.. code-block:: bash
+
+    pip3 install --upgrade pip
+    pip3 install gcovr
+
 .. _getting_started_get_source:
 
 #########################
diff --git a/plat/host/host_test/CMakeLists.txt b/plat/host/host_test/CMakeLists.txt
index c28fb13..0e477c6 100644
--- a/plat/host/host_test/CMakeLists.txt
+++ b/plat/host/host_test/CMakeLists.txt
@@ -9,6 +9,7 @@
 arm_config_option(
     NAME RMM_UNITTESTS
     HELP "Enable Unitests for the build"
+    TYPE BOOL
     DEFAULT "ON"
     TYPE INTERNAL)