Skip to content
Open
1 change: 1 addition & 0 deletions M2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ include(prechecks) ## CMake macros for preprocessor checks and linting
include(CTest) ## CMake module for generating a 'test' target for all unit-tests
include(profiling) ## CMake macros for profiling Macaulay2 code
include(configure) ## CMake script for configuring various variables
include(gcov) ## CMake macros for gcc/gcov code coverage (needs GCOV from configure)
include(check-libraries) ## CMake script for checking which libraries exist and which need to be built
include(build-libraries) ## CMake script for downloading, building, and installing external libraries
include(startup) ## CMake script for messy substitutions in Macaulay2/bin/startup.c
Expand Down
34 changes: 34 additions & 0 deletions M2/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,38 @@ unmark-packages:; $(MAKE) -C Macaulay2/packages $@
reconfigure: reconfigure-top-only unconfigure-libs
unconfigure-libs:; $(MAKE) -C libraries unconfigure
remove-deps:; find . -name \*.dep -delete

## gcc/gcov code coverage (configure with --enable-gcov).
## Coverage data (.gcda) is generated automatically whenever a binary built in
## coverage mode exits -- run the unit tests, "make check", or the M2 binary on
## your own code. These targets only clear and report on the accumulated data.
## By default the report covers the whole source tree; narrow it by overriding
## COVERAGE_ROOT and COVERAGE_OBJDIR (e.g. =@srcdir@/Macaulay2/e and
## =Macaulay2/e for just the engine).
GCOVR = gcovr
COVERAGE_DIR = coverage
COVERAGE_ROOT = @srcdir@
# COVERAGE_OBJDIR is passed to gcovr as a positional search path, NOT via
# --object-directory: the latter forces gcov to run in a single directory, which
# can't resolve sources for objects built in deeper VPATH subdirectories (e.g.
# Macaulay2/e/unit-tests). As a search path, gcovr runs gcov in each data file's
# own directory, so every relative source path resolves.
COVERAGE_OBJDIR = @builddir@
COVERAGE_INDEX = $(CURDIR)/$(COVERAGE_DIR)/index.html
# gcov can report a function on more than one line (e.g. inlines built at -O0);
# merge those instead of erroring, attributing the function to its first line.
COVERAGE_MERGE_MODE = merge-use-line-min
# Extra options for gcovr, e.g. GCOVR_OPTIONS='--filter Macaulay2/e/' to scope
# the report to the engine, or '--exclude .*/unit-tests/.*' to drop test sources.
GCOVR_OPTIONS =
.PHONY: coverage-reset coverage-report
coverage-reset:; find . -name \*.gcda -delete
coverage-report:
@ $(MKDIR_P) $(COVERAGE_DIR)
$(GCOVR) --root $(COVERAGE_ROOT) $(COVERAGE_OBJDIR) \
--merge-mode-functions=$(COVERAGE_MERGE_MODE) $(GCOVR_OPTIONS) \
--html-details $(COVERAGE_DIR)/index.html --print-summary
@ printf '%s\033]8;;file://%s%s\033\\%s\033]8;;\033\\\n' '-- coverage report written to ' "$$(hostname)" "$(COVERAGE_INDEX)" "$(COVERAGE_INDEX)"
find-conflicts:; grep -r -nH --exclude-dir BUILD -e '^<<<<<<< ' @srcdir@ || true
log-archive:; find . -name config.log |xargs tar xzf config-logs.tgz
shell:; PKG_CONFIG_PATH=$(M2_PKG_CONFIG_PATH) bash
Expand Down Expand Up @@ -208,6 +240,8 @@ help:
@ echo " relink-nostrip remove M2@EXE@ and rebuild it, unstripped"
@ echo " install make and install files"
@ echo " check run the tests"
@ echo " coverage-reset clear accumulated gcov coverage data (requires --enable-gcov)"
@ echo " coverage-report generate an HTML coverage report with gcovr"
@ echo " clean remove all generated files except configured files"
@ echo " distclean remove all generated files"
@ echo " dist generate a source tarball for distribution"
Expand Down
7 changes: 7 additions & 0 deletions M2/cmake/configure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ option(LINTING "Enable linting source files" OFF)
option(MEMDEBUG "Enable memory allocation debugging" OFF)
option(PROFILING "Enable profiling build flags" OFF)
option(COVERAGE "Enable Clang code coverage test" OFF)
option(GCOV "Enable gcc/gcov code coverage" OFF)
option(GIT_SUBMODULE "Update submodules during build" ON)
option(BUILD_NATIVE "Use native SIMD instructions" ON)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
Expand Down Expand Up @@ -106,6 +107,7 @@ message("## Configure Macaulay2
BUILD_TESTING = ${BUILD_TESTING}
BUILD_DOCS = ${BUILD_DOCS}\n
COVERAGE = ${COVERAGE}
GCOV = ${GCOV}
MEMDEBUG = ${MEMDEBUG}
PROFILING = ${PROFILING}\n
DEVELOPMENT = ${DEVELOPMENT}
Expand Down Expand Up @@ -200,6 +202,11 @@ if(PROFILING)
add_compile_options(-pg)
add_link_options(-pg)
endif()
if(GCOV)
# gcc/gcov coverage: --coverage instruments; -O0 keeps line attribution meaningful.
add_compile_options(--coverage -O0)
add_link_options(--coverage)
endif()

# Flags based on build type
# Note: certain flags are initialized by CMake based on the compiler and build type.
Expand Down
2 changes: 1 addition & 1 deletion M2/cmake/coverage.cmake
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
###############################################################################
# See https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
find_program(LLVM_PROFDATA NAMES llvm-profdata)
find_program(LLVM_COV NAMES clang-format)
find_program(LLVM_COV NAMES llvm-cov)

set(_coverage_dir ${CMAKE_BINARY_DIR}/coverage)

Expand Down
56 changes: 56 additions & 0 deletions M2/cmake/gcov.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# gcc/gcov code coverage (configure with -DGCOV=ON).
# Instrumentation flags are added in configure.cmake; this module only provides
# convenience targets to clear and report on the accumulated .gcda data. The data
# itself is generated automatically whenever a binary built in coverage mode exits
# (the unit tests, ctest, or the M2 binary run on your own code).
#
# By default the report covers the whole source tree; pass extra gcovr options
# via GCOVR_OPTIONS, e.g. -DGCOVR_OPTIONS="--filter Macaulay2/e/" to scope it to
# the engine, or "--exclude .*/unit-tests/.*" to drop the test sources.
#
# Usage:
# cmake -S . -B BUILD/cov -GNinja -DGCOV=ON -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
# cmake --build BUILD/cov --target M2-engine M2-unit-tests
# cmake --build BUILD/cov --target coverage-reset # start from a clean slate
# ctest --test-dir BUILD/cov -R unit-tests # (or run M2 however you like)
# cmake --build BUILD/cov --target coverage-report # writes coverage/index.html

if(GCOV)
find_program(GCOVR NAMES gcovr)
set(_coverage_dir ${CMAKE_BINARY_DIR}/coverage)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a small CI smoke test with coverage enabled, especially for the CMake path? The current checks all use the default GCOV=OFF, so they don't exercise the instrumentation flags or either coverage target; the PR also notes that the CMake support hasn't been tested. Even a minimal configure/build/run/coverage-report job would catch toolchain and gcovr invocation problems.

AI-assisted review comment made by GPT 4.6-Sol under Andrew's direction in order to observe deeper code reaches.

set(_coverage_index ${_coverage_dir}/index.html)
set(GCOVR_OPTIONS "" CACHE STRING
"Extra options passed to gcovr for the coverage-report target")
separate_arguments(_gcovr_options UNIX_COMMAND "${GCOVR_OPTIONS}")

# coverage-reset clears all accumulated data; the M2 binary may have written
# .gcda anywhere in the tree, so this is deliberately not engine-scoped.
add_custom_target(coverage-reset
COMMENT "Deleting accumulated .gcda coverage counters"
COMMAND find ${CMAKE_BINARY_DIR} -name "*.gcda" -delete)

if(NOT GCOVR)
message(WARNING "GCOV is ON but gcovr was not found; the 'coverage-report' target will not be created. Install gcovr (e.g. 'pip install gcovr').")
else()
add_custom_target(coverage-report
COMMENT "Generating gcov/gcovr coverage report"
COMMAND ${CMAKE_COMMAND} -E make_directory ${_coverage_dir}
# Pass the build tree as a positional search path (not --object-directory)
# so gcovr runs gcov in each data file's own directory, matching the
# autotools target.
COMMAND ${GCOVR}
--root ${CMAKE_SOURCE_DIR}
${CMAKE_BINARY_DIR}
# gcov can report a function on multiple lines (inlines at -O0); merge
# those instead of erroring, attributing the function to its first line.
--merge-mode-functions=merge-use-line-min
${_gcovr_options}
--html-details ${_coverage_index}
--print-summary
# Print the report path as an OSC 8 terminal hyperlink so it is clickable.
# The printf format is a bracket argument (no CMake escaping); the report
# path is passed as $1 to sh.
COMMAND sh -c [==[printf '%s\033]8;;file://%s%s\033\\%s\033]8;;\033\\\n' 'Coverage report: ' "$(hostname)" "$1" "$1"]==] sh "${_coverage_index}"
USES_TERMINAL)
endif()
endif()
19 changes: 19 additions & 0 deletions M2/configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,24 @@ AS_IF([test $BUILD_lapack = yes],
LAPACK_LIBS=-llapack])
AC_SUBST([LINALGLIBS], ["$LAPACK_LIBS $BLAS_LIBS"])

dnl Apply gcov coverage flags here, *after* the Fortran/BLAS/LAPACK library
dnl detection: gfortran expands --coverage to -lgcov, which would otherwise be
dnl captured into FCLIBS/BLAS_LIBS and clash with the compiler's own coverage
dnl runtime (e.g. clang's libclang_rt.profile) at link time.
AC_SUBST([GCOV],[no])
AC_ARG_ENABLE([gcov],
[AS_HELP_STRING([--enable-gcov],
[enable gcc/gcov code coverage (implies -O0 and disables stripping)])],
[GCOV=$enableval])
AS_IF([test "$GCOV" = yes],
[# --coverage implies -fprofile-arcs -ftest-coverage (compile) and
# -lgcov (link); -O0 keeps line/branch attribution meaningful.
CFLAGS="$CFLAGS --coverage -O0"
CXXFLAGS="$CXXFLAGS --coverage -O0"
LDFLAGS="$LDFLAGS --coverage"
ENABLE_STRIP=no
OPTIMIZE=no])

AS_IF([test $BUILD_givaro = no],
[AC_LANG([C++])
AC_CHECK_HEADER([givaro/givinteger.h],
Expand Down Expand Up @@ -1892,6 +1910,7 @@ AC_MSG_NOTICE([with OS REL = $OS $REL])
AC_MSG_NOTICE([with ARCH = $ARCH])
AC_MSG_NOTICE([with OPTIMIZE = $OPTIMIZE])
AC_MSG_NOTICE([with DEBUG = $DEBUG])
AC_MSG_NOTICE([with GCOV = $GCOV])
AC_MSG_NOTICE([with GIT_DESCRIPTION = $GIT_DESCRIPTION])
AC_MSG_NOTICE([with GIT_BRANCH = $GIT_BRANCH])
AC_MSG_NOTICE([with DOCUMENTATION = $DOCUMENTATION])
Expand Down