Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: C++ Unit Tests with Coverage

on:
push:
branches: [ main, develop, copilot/** ]
pull_request:
branches: [ main, develop ]
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
mpi_ranks: [1, 4, 8]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
cmake \
g++ \
libopenmpi-dev \
openmpi-bin \
petsc-dev \
lcov

- name: Configure CMake
working-directory: ${{github.workspace}}/code/cpp
run: |
mkdir -p build
cd build
cmake -DENABLE_COVERAGE=ON ..

- name: Build main application
working-directory: ${{github.workspace}}/code/cpp/build
run: |
echo "Building main application..."
make -j$(nproc) cellcollectives || exit 1
echo "✅ Main application built successfully"

- name: Build tests
working-directory: ${{github.workspace}}/code/cpp/build
run: make -j$(nproc) unit_tests

- name: Run unit tests (MPI ranks=${{ matrix.mpi_ranks }})
working-directory: ${{github.workspace}}/code/cpp/build
run: |
if [ "${{ matrix.mpi_ranks }}" -eq 1 ]; then
echo "Running tests in single-process mode..."
./tests/unit_tests
else
echo "Running tests with MPI (${{ matrix.mpi_ranks }} ranks)..."
mpirun --allow-run-as-root --oversubscribe -np ${{ matrix.mpi_ranks }} ./tests/unit_tests
fi
echo "✅ Tests completed with ${{ matrix.mpi_ranks }} rank(s)"

- name: Generate coverage report
if: matrix.mpi_ranks == 1
working-directory: ${{github.workspace}}/code/cpp/build
run: |
# Capture coverage data
lcov --capture --directory . --output-file coverage.info --ignore-errors mismatch

# Filter out system files and test files
lcov --remove coverage.info '/usr/*' '*/build/_deps/*' '*/tests/*' \
--output-file coverage_filtered.info --ignore-errors unused

# Generate HTML report
genhtml coverage_filtered.info --output-directory coverage_report

# Print summary
echo "## Code Coverage Summary" >> $GITHUB_STEP_SUMMARY
lcov --summary coverage_filtered.info 2>&1 | tee -a $GITHUB_STEP_SUMMARY

- name: Upload coverage report
if: matrix.mpi_ranks == 1
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: ${{github.workspace}}/code/cpp/build/coverage_report/
retention-days: 30

- name: Comment coverage on PR
if: github.event_name == 'pull_request' && matrix.mpi_ranks == 1
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const coverage = fs.readFileSync('${{github.workspace}}/code/cpp/build/coverage_filtered.info', 'utf8');

// Parse coverage data
const lines = coverage.match(/LF:(\d+)/g);
const linesHit = coverage.match(/LH:(\d+)/g);

if (lines && linesHit) {
const totalLines = lines.reduce((sum, l) => sum + parseInt(l.split(':')[1]), 0);
const hitLines = linesHit.reduce((sum, l) => sum + parseInt(l.split(':')[1]), 0);
const percentage = totalLines > 0 ? ((hitLines / totalLines) * 100).toFixed(2) : 0;

const comment = `## Test Results ✅\n\n` +
`All tests passed! 🎉\n\n` +
`**Coverage:** ${percentage}% (${hitLines}/${totalLines} lines)\n\n` +
`**MPI Testing:** Tests validated with 1, 4, and 8 ranks\n\n` +
`[View detailed coverage report](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}})`;

github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
}
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
**/vtk_output/
.idea
build
build
*.gcda
*.gcno
*.gcov
coverage_report/
coverage.info
coverage_filtered.info
12 changes: 12 additions & 0 deletions code/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,17 @@ endif ()

enable_testing()

# Fetch Google Test
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

# autopas
add_subdirectory(src)
add_subdirectory(tests)
142 changes: 83 additions & 59 deletions code/cpp/cmake/modules/petsc.cmake
Original file line number Diff line number Diff line change
@@ -1,73 +1,97 @@
cmake_minimum_required(VERSION 3.20.3)

# Define the PETSc external project
include(ExternalProject)

set(PETSC_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/petsc")
# Try to use system PETSc first
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules(PETSC_PKG PETSc)
endif()

# if debug is true, set PETSC_ARCH to arch-linux-c-debug
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
set(PETSC_ARCH "arch-linux-c-debug")
# If system PETSc is found, use it
if(PETSC_PKG_FOUND)
message(STATUS "Using system PETSc")
set(PETSC_INCLUDE_DIRS ${PETSC_PKG_INCLUDE_DIRS})
set(PETSC_LIBRARIES ${PETSC_PKG_LIBRARIES})
set(PETSC_LIBRARY_DIRS ${PETSC_PKG_LIBRARY_DIRS})

# Create an empty target for consistency
add_custom_target(petsc)

include_directories(${PETSC_INCLUDE_DIRS})
link_directories(${PETSC_LIBRARY_DIRS})

else()
set(PETSC_ARCH "arch-linux-c-opt")
endif()
# Fall back to downloading and building PETSc
message(STATUS "System PETSc not found, will download and build")

# Define the PETSc external project
include(ExternalProject)

set(PETSC_LIB_DIR "${PETSC_BUILD_DIR}/${PETSC_ARCH}/lib")
set(PETSC_LIBRARIES "${PETSC_LIB_DIR}/libpetsc.so") # or .a if static
set(PETSC_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/petsc")

# Check if PETSc is already built
if(EXISTS "${PETSC_LIBRARIES}")
message(STATUS "PETSc library already exists at ${PETSC_LIBRARIES}")
set(PETSC_ALREADY_BUILT TRUE)
else()
set(PETSC_ALREADY_BUILT FALSE)
endif()
# if debug is true, set PETSC_ARCH to arch-linux-c-debug
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
set(PETSC_ARCH "arch-linux-c-debug")
else()
set(PETSC_ARCH "arch-linux-c-opt")
endif()

# print if library exists
message(STATUS "PETSc library exists: ${PETSC_LIBRARIES}")
set(PETSC_LIB_DIR "${PETSC_BUILD_DIR}/${PETSC_ARCH}/lib")
set(PETSC_LIBRARIES "${PETSC_LIB_DIR}/libpetsc.so") # or .a if static

# Download, configure, and build PETSc only if not already built
if(NOT PETSC_ALREADY_BUILT)
ExternalProject_Add(petsc_external
GIT_REPOSITORY https://gitlab.com/petsc/petsc.git
GIT_TAG release
SOURCE_DIR "${PETSC_BUILD_DIR}"
PREFIX "petsc"
CONFIGURE_COMMAND ./configure --with-debugging=$<IF:$<CONFIG:Debug>,1,0> --with-fc=0 --download-f2cblaslapack
BUILD_COMMAND make all
INSTALL_COMMAND ""
BUILD_IN_SOURCE 1
LOG_DOWNLOAD ON
LOG_CONFIGURE ON
LOG_BUILD ON
LOG_DIR "${CMAKE_CURRENT_BINARY_DIR}/logs"
STAMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/stamp"
)
# Check if PETSc is already built
if(EXISTS "${PETSC_LIBRARIES}")
message(STATUS "PETSc library already exists at ${PETSC_LIBRARIES}")
set(PETSC_ALREADY_BUILT TRUE)
else()
set(PETSC_ALREADY_BUILT FALSE)
endif()

# Create a custom target that can be used as a dependency
add_custom_target(petsc DEPENDS petsc_external)
else()
# Create an empty target if PETSc is already built
add_custom_target(petsc)
endif()
# print if library exists
message(STATUS "PETSc library exists: ${PETSC_LIBRARIES}")

# Set PETSC_DIR to the build directory
set(PETSC_DIR "${PETSC_BUILD_DIR}")
# Download, configure, and build PETSc only if not already built
if(NOT PETSC_ALREADY_BUILT)
ExternalProject_Add(petsc_external
GIT_REPOSITORY https://gitlab.com/petsc/petsc.git
GIT_TAG v3.20.2
SOURCE_DIR "${PETSC_BUILD_DIR}"
PREFIX "petsc"
CONFIGURE_COMMAND ./configure --with-debugging=$<IF:$<CONFIG:Debug>,1,0> --with-fc=0 --download-f2cblaslapack
BUILD_COMMAND make all
INSTALL_COMMAND ""
BUILD_IN_SOURCE 1
LOG_DOWNLOAD ON
LOG_CONFIGURE ON
LOG_BUILD ON
LOG_DIR "${CMAKE_CURRENT_BINARY_DIR}/logs"
STAMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/stamp"
)

# Set PETSc include and library directories
set(PETSC_INCLUDE_DIRS "${PETSC_DIR}/include;${PETSC_DIR}/${PETSC_ARCH}/include")
# Create a custom target that can be used as a dependency
add_custom_target(petsc DEPENDS petsc_external)
else()
# Create an empty target if PETSc is already built
add_custom_target(petsc)
endif()

include_directories(${PETSC_INCLUDE_DIRS})
link_directories(${PETSC_LIB_DIR})
# Set PETSC_DIR to the build directory
set(PETSC_DIR "${PETSC_BUILD_DIR}")

# Optionally, add PETSc definitions
add_definitions(-DPETSC_USE_EXTERN_CXX)
# Set PETSc include and library directories
set(PETSC_INCLUDE_DIRS "${PETSC_DIR}/include;${PETSC_DIR}/${PETSC_ARCH}/include")

include_directories(${PETSC_INCLUDE_DIRS})
link_directories(${PETSC_LIB_DIR})

# Add a custom command to check for PETSc library after build
add_custom_command(
OUTPUT "${PETSC_LIBRARIES}"
COMMAND ${CMAKE_COMMAND} -E echo "Checking for PETSc library..."
COMMAND ${CMAKE_COMMAND} -E make_directory "${PETSC_LIB_DIR}"
DEPENDS petsc
COMMENT "Waiting for PETSc library to be built..."
)
# Add a custom command to check for PETSc library after build
add_custom_command(
OUTPUT "${PETSC_LIBRARIES}"
COMMAND ${CMAKE_COMMAND} -E echo "Checking for PETSc library..."
COMMAND ${CMAKE_COMMAND} -E make_directory "${PETSC_LIB_DIR}"
DEPENDS petsc
COMMENT "Waiting for PETSc library to be built..."
)
endif()

# Optionally, add PETSc definitions
add_definitions(-DPETSC_USE_EXTERN_CXX)
26 changes: 19 additions & 7 deletions code/cpp/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,30 @@ file(
"*.h"
)

# Create executable with main.cpp and all other sources
add_executable(cellcollectives main.cpp ${MY_SRC})
# Remove main.cpp from the library sources
list(FILTER MY_SRC EXCLUDE REGEX ".*main\\.cpp$")

# WORKAROUND: Domain.cpp is not picked up by GLOB_RECURSE for unknown reasons
# Explicitly add it to ensure it's included in the library
list(APPEND MY_SRC "${CMAKE_CURRENT_SOURCE_DIR}/spatial/Domain.cpp")

include(petsc)
find_package(MPI REQUIRED)
find_package(OpenMP REQUIRED)

# Create a library with all source files (excluding main.cpp)
add_library(cellcollectives_lib STATIC ${MY_SRC})

if(OpenMP_CXX_FOUND)
set(OpenMP_STATIC_LIB ON)
target_link_libraries(cellcollectives PUBLIC OpenMP::OpenMP_CXX)
target_link_libraries(cellcollectives_lib PUBLIC OpenMP::OpenMP_CXX)
endif()

# Add dependency on petsc target
add_dependencies(cellcollectives petsc)
add_dependencies(cellcollectives_lib petsc)

target_include_directories(
cellcollectives
cellcollectives_lib
PUBLIC
${CELL_COLLECTIVES_SOURCE_DIR}/src/
${PETSC_INCLUDE_DIRS}
Expand All @@ -30,7 +38,7 @@ target_include_directories(
)

target_link_libraries(
cellcollectives
cellcollectives_lib
PUBLIC
${PETSC_LIBRARIES}
${MPI_CXX_LIBRARIES}
Expand All @@ -39,8 +47,12 @@ target_link_libraries(
)

target_compile_definitions(
cellcollectives
cellcollectives_lib
PRIVATE
${MPI_CXX_COMPILE_DEFINITIONS}
${OpenMP_CXX_COMPILE_DEFINITIONS}
)

# Create executable that links to the library
add_executable(cellcollectives main.cpp)
target_link_libraries(cellcollectives PRIVATE cellcollectives_lib)
30 changes: 30 additions & 0 deletions code/cpp/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Enable code coverage if requested
option(ENABLE_COVERAGE "Enable code coverage" OFF)
if(ENABLE_COVERAGE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -fprofile-arcs -ftest-coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
endif()

# Find all test source files
file(GLOB TEST_SOURCES "*.cpp")

# Create test executable
add_executable(unit_tests ${TEST_SOURCES})

# Link against Google Test, the library, and other dependencies
target_link_libraries(unit_tests
PRIVATE
GTest::gtest_main
cellcollectives_lib
)

# Include test directories
target_include_directories(unit_tests
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CELL_COLLECTIVES_SOURCE_DIR}/src/
)

# Discover tests for CTest
include(GoogleTest)
gtest_discover_tests(unit_tests)
Loading
Loading