Skip to content

Commit bcb43cb

Browse files
committed
refactor github workflow: add LLVM coverage support and update test/run logic for compatibility and flexibility
1 parent 7c666be commit bcb43cb

5 files changed

Lines changed: 180 additions & 75 deletions

File tree

.github/workflows/ubuntu.yml

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ jobs:
331331
env:
332332
PPC_NUM_PROC: 1
333333
PPC_ASAN_RUN: 1
334-
gcc-build-codecov:
334+
clang-build-codecov:
335335
# needs:
336336
# - gcc-test-extended
337337
# - clang-test-extended
@@ -343,65 +343,105 @@ jobs:
343343
- name: Setup environment
344344
run: |
345345
sudo apt-get update
346-
sudo apt-get install --no-install-recommends -y \
347-
gcc-14 g++-14 ninja-build libmpich-dev libomp-dev valgrind gcovr
346+
sudo apt-get install --no-install-recommends -y ninja-build libmpich-dev libomp-dev valgrind python3-pip
347+
wget https://apt.llvm.org/llvm.sh
348+
chmod u+x llvm.sh
349+
sudo ./llvm.sh 20 all
350+
python3 -m pip install -r requirements.txt
348351
- name: ccache
349352
uses: hendrikmuhs/ccache-action@v1.2
350353
with:
351-
key: ${{ runner.os }}-gcc
354+
key: ${{ runner.os }}-clang-coverage
352355
create-symlink: true
353356
max-size: 1G
354357
- name: CMake configure
355358
run: >
356359
cmake -S . -B build -G Ninja
357360
-D CMAKE_C_COMPILER_LAUNCHER=ccache -D CMAKE_CXX_COMPILER_LAUNCHER=ccache
358361
-D CMAKE_BUILD_TYPE=RELEASE
359-
-D CMAKE_VERBOSE_MAKEFILE=ON -D USE_COVERAGE=ON
362+
-D CMAKE_VERBOSE_MAKEFILE=ON -D USE_LLVM_COVERAGE=ON
363+
env:
364+
CC: clang-20
365+
CXX: clang++-20
360366
- name: Build project
361367
run: |
362368
cmake --build build --parallel
369+
env:
370+
CC: clang-20
371+
CXX: clang++-20
363372
- name: Run tests (MPI)
364-
run: scripts/run_tests.py --running-type="processes_coverage"
373+
run: scripts/run_tests.py --running-type="processes_coverage" --counts 2
365374
env:
366-
PPC_NUM_PROC: 2
367375
PPC_NUM_THREADS: 2
376+
LLVM_PROFILE_FILE: "build/llvm_profile_%p_%m.profraw"
368377
- name: Run tests (threads)
369378
run: scripts/run_tests.py --running-type="threads" --counts 1 2 3 4
370379
env:
371380
PPC_NUM_PROC: 1
372-
- name: Generate gcovr Coverage Data
381+
LLVM_PROFILE_FILE: "build/llvm_profile_%p_%m.profraw"
382+
- name: Generate LLVM Coverage Data
373383
run: |
374384
mkdir cov-report
375385
cd build
376-
# Collect coverage data from all MPI rank directories and regular build directory
377-
# Copy coverage files from MPI rank directories to main build directory
378-
# Only copy files that don't already exist to avoid conflicts
379-
if [ -d "gcov_data" ]; then
380-
find gcov_data -name "*.gcda" -exec bash -c 'cp "$1" "$(basename "$1")" 2>/dev/null || true' _ {} \;
381-
find gcov_data -name "*.gcno" -exec bash -c 'cp "$1" "$(basename "$1")" 2>/dev/null || true' _ {} \;
386+
# Collect all profile data files (including from MPI ranks)
387+
PROFILE_FILES=$(find . -name "*.profraw" -type f | tr '\n' ' ')
388+
389+
if [ -z "$PROFILE_FILES" ]; then
390+
echo "No profile files found!"
391+
exit 1
382392
fi
383-
384-
gcovr -r ../ \
385-
--gcov-object-directory . \
386-
--exclude '.*3rdparty/.*' \
387-
--exclude '/usr/.*' \
388-
--exclude '.*tasks/.*/tests/.*' \
389-
--exclude '.*modules/.*/tests/.*' \
390-
--exclude '.*tasks/common/runners/.*' \
391-
--exclude '.*modules/runners/.*' \
392-
--exclude '.*modules/util/include/perf_test_util.hpp' \
393-
--exclude '.*modules/util/include/func_test_util.hpp' \
394-
--exclude '.*modules/util/src/func_test_util.cpp' \
395-
--gcov-ignore-parse-errors \
396-
--gcov-ignore-errors \
397-
--xml --output ../coverage.xml \
398-
--html=../cov-report/index.html \
399-
--html-details=../cov-report/ \
400-
.
393+
394+
# Merge all profile data files
395+
llvm-profdata-20 merge -sparse $PROFILE_FILES -o merged.profdata
396+
397+
# Generate coverage summary
398+
llvm-cov-20 report \
399+
-instr-profile=merged.profdata \
400+
bin/ppc_func_tests bin/core_func_tests \
401+
-ignore-filename-regex='.*3rdparty/.*' \
402+
-ignore-filename-regex='/usr/.*' \
403+
-ignore-filename-regex='.*tasks/.*/tests/.*' \
404+
-ignore-filename-regex='.*modules/.*/tests/.*' \
405+
-ignore-filename-regex='.*tasks/common/runners/.*' \
406+
-ignore-filename-regex='.*modules/runners/.*'
407+
408+
# Generate LCOV report for Codecov
409+
llvm-cov-20 export \
410+
-instr-profile=merged.profdata \
411+
bin/ppc_func_tests bin/core_func_tests \
412+
-format=lcov \
413+
-ignore-filename-regex='.*3rdparty/.*' \
414+
-ignore-filename-regex='/usr/.*' \
415+
-ignore-filename-regex='.*tasks/.*/tests/.*' \
416+
-ignore-filename-regex='.*modules/.*/tests/.*' \
417+
-ignore-filename-regex='.*tasks/common/runners/.*' \
418+
-ignore-filename-regex='.*modules/runners/.*' \
419+
-ignore-filename-regex='.*modules/util/include/perf_test_util.hpp' \
420+
-ignore-filename-regex='.*modules/util/include/func_test_util.hpp' \
421+
-ignore-filename-regex='.*modules/util/src/func_test_util.cpp' \
422+
> ../coverage.lcov
423+
424+
# Generate HTML report
425+
llvm-cov-20 show \
426+
-instr-profile=merged.profdata \
427+
bin/ppc_func_tests bin/core_func_tests \
428+
-format=html \
429+
-output-dir=../cov-report \
430+
-show-line-counts-or-regions \
431+
-show-instantiations \
432+
-ignore-filename-regex='.*3rdparty/.*' \
433+
-ignore-filename-regex='/usr/.*' \
434+
-ignore-filename-regex='.*tasks/.*/tests/.*' \
435+
-ignore-filename-regex='.*modules/.*/tests/.*' \
436+
-ignore-filename-regex='.*tasks/common/runners/.*' \
437+
-ignore-filename-regex='.*modules/runners/.*' \
438+
-ignore-filename-regex='.*modules/util/include/perf_test_util.hpp' \
439+
-ignore-filename-regex='.*modules/util/include/func_test_util.hpp' \
440+
-ignore-filename-regex='.*modules/util/src/func_test_util.cpp'
401441
- name: Upload coverage reports to Codecov
402442
uses: codecov/codecov-action@v5.4.3
403443
with:
404-
files: coverage.xml
444+
files: coverage.lcov
405445
- name: Upload coverage report artifact
406446
id: upload-cov
407447
uses: actions/upload-artifact@v4

cmake/configure.cmake

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
2828

2929
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
3030

31-
if(USE_COVERAGE)
31+
if(USE_COVERAGE OR USE_LLVM_COVERAGE)
3232
set(CMAKE_INSTALL_RPATH "${CMAKE_BINARY_DIR}/ppc_onetbb/install/lib")
3333
else()
3434
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
@@ -75,6 +75,10 @@ if(UNIX)
7575
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage")
7676
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
7777
endif(USE_COVERAGE)
78+
if(USE_LLVM_COVERAGE)
79+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-instr-generate -fcoverage-mapping")
80+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate -fcoverage-mapping")
81+
endif(USE_LLVM_COVERAGE)
7882
endif()
7983

8084
if(MSVC)

scripts/run_tests.py

Lines changed: 68 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,29 @@ def run_processes(self, additional_mpi_args):
146146
+ self.__get_gtest_settings(10, '_' + task_type)
147147
)
148148

149+
def __create_coverage_wrapper(self, template_name, replacements):
150+
"""Create a coverage wrapper script from the template."""
151+
template_path = (
152+
Path(self.__get_project_path()) / "scripts" / "templates" / template_name
153+
)
154+
wrapper_path = (
155+
Path(self.__get_project_path()) / "build" / template_name.replace('.template', '')
156+
)
157+
158+
# Read template
159+
with open(template_path, 'r', encoding='utf-8') as template_file:
160+
content = template_file.read()
161+
162+
# Replace placeholders
163+
for key, value in replacements.items():
164+
content = content.replace(f"{{{key}}}", value)
165+
166+
# Write a wrapper script
167+
wrapper_path.write_text(content)
168+
wrapper_path.chmod(0o755)
169+
170+
return wrapper_path
171+
149172
def run_processes_coverage(self, additional_mpi_args):
150173
"""Run tests in multiprocessing mode with a coverage collection."""
151174
ppc_num_proc = self.__ppc_env.get("PPC_NUM_PROC")
@@ -156,50 +179,54 @@ def run_processes_coverage(self, additional_mpi_args):
156179

157180
# Set up coverage environment for MPI processes
158181
if not self.__ppc_env.get("PPC_ASAN_RUN"):
159-
# Enable coverage data collection for each MPI process
160-
self.__ppc_env["GCOV_PREFIX_STRIP"] = "0"
161-
# Use MPI rank to create unique coverage directories for each process
162-
gcov_base_dir = Path(self.__get_project_path()) / "build" / "gcov_data"
163-
gcov_base_dir.mkdir(parents=True, exist_ok=True)
164-
165-
# Set GCOV_PREFIX to include MPI rank - this creates separate directories
166-
# for each MPI process at runtime
167-
self.__ppc_env["GCOV_PREFIX"] = str(
168-
gcov_base_dir / "rank_${PMI_RANK:-${OMPI_COMM_WORLD_RANK:-${SLURM_PROCID:-0}}}"
169-
)
182+
# Check if we're using LLVM coverage or gcov
183+
llvm_profile_file = self.__ppc_env.get("LLVM_PROFILE_FILE")
184+
185+
if llvm_profile_file:
186+
# LLVM coverage setup
187+
wrapper_script = self.__create_coverage_wrapper(
188+
"mpi_llvm_coverage_wrapper.sh.template",
189+
{"llvm_profile_base": llvm_profile_file.replace('%p', '%p').replace('%m', '%m')}
190+
)
170191

171-
# Create a wrapper script to set a unique prefix per process
172-
wrapper_script = Path(self.__get_project_path()) / "build" / "mpi_coverage_wrapper.sh"
173-
wrapper_content = f"""#!/bin/bash
174-
# Get MPI rank from environment variables
175-
if [ -n "$PMIX_RANK" ]; then
176-
RANK=$PMIX_RANK
177-
elif [ -n "$PMI_RANK" ]; then
178-
RANK=$PMI_RANK
179-
elif [ -n "$OMPI_COMM_WORLD_RANK" ]; then
180-
RANK=$OMPI_COMM_WORLD_RANK
181-
elif [ -n "$SLURM_PROCID" ]; then
182-
RANK=$SLURM_PROCID
183-
else
184-
RANK=0
185-
fi
186-
187-
export GCOV_PREFIX="{gcov_base_dir}/rank_$RANK"
188-
mkdir -p "$GCOV_PREFIX"
189-
exec "$@"
190-
"""
191-
wrapper_script.write_text(wrapper_content)
192-
wrapper_script.chmod(0o755)
192+
# Run tests with the LLVM coverage wrapper
193+
for task_type in ["all", "mpi"]:
194+
test_command = (
195+
mpi_running
196+
+ [str(wrapper_script)]
197+
+ [str(self.work_dir / 'ppc_func_tests')]
198+
+ self.__get_gtest_settings(10, '_' + task_type)
199+
)
200+
self.__run_exec(test_command)
201+
else:
202+
# Original gcov coverage setup
203+
# Enable coverage data collection for each MPI process
204+
self.__ppc_env["GCOV_PREFIX_STRIP"] = "0"
205+
# Use MPI rank to create unique coverage directories for each process
206+
gcov_base_dir = Path(self.__get_project_path()) / "build" / "gcov_data"
207+
gcov_base_dir.mkdir(parents=True, exist_ok=True)
208+
209+
# Set GCOV_PREFIX to include MPI rank - this creates separate directories
210+
# for each MPI process at runtime
211+
self.__ppc_env["GCOV_PREFIX"] = str(
212+
gcov_base_dir / "rank_${PMI_RANK:-${OMPI_COMM_WORLD_RANK:-${SLURM_PROCID:-0}}}"
213+
)
193214

194-
# Run tests with a coverage wrapper
195-
for task_type in ["all", "mpi"]:
196-
test_command = (
197-
mpi_running
198-
+ [str(wrapper_script)]
199-
+ [str(self.work_dir / 'ppc_func_tests')]
200-
+ self.__get_gtest_settings(10, '_' + task_type)
215+
# Create a wrapper script to set a unique prefix per process
216+
wrapper_script = self.__create_coverage_wrapper(
217+
"mpi_gcov_coverage_wrapper.sh.template",
218+
{"gcov_base_dir": str(gcov_base_dir)}
201219
)
202-
self.__run_exec(test_command)
220+
221+
# Run tests with a coverage wrapper
222+
for task_type in ["all", "mpi"]:
223+
test_command = (
224+
mpi_running
225+
+ [str(wrapper_script)]
226+
+ [str(self.work_dir / 'ppc_func_tests')]
227+
+ self.__get_gtest_settings(10, '_' + task_type)
228+
)
229+
self.__run_exec(test_command)
203230

204231
def run_performance(self):
205232
"""Run performance tests."""
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
# Get MPI rank from environment variables
3+
if [ -n "$PMIX_RANK" ]; then
4+
RANK=$PMIX_RANK
5+
elif [ -n "$PMI_RANK" ]; then
6+
RANK=$PMI_RANK
7+
elif [ -n "$OMPI_COMM_WORLD_RANK" ]; then
8+
RANK=$OMPI_COMM_WORLD_RANK
9+
elif [ -n "$SLURM_PROCID" ]; then
10+
RANK=$SLURM_PROCID
11+
else
12+
RANK=0
13+
fi
14+
15+
export GCOV_PREFIX="{gcov_base_dir}/rank_$RANK"
16+
mkdir -p "$GCOV_PREFIX"
17+
exec "$@"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
# Get MPI rank from environment variables
3+
if [ -n "$PMIX_RANK" ]; then
4+
RANK=$PMIX_RANK
5+
elif [ -n "$PMI_RANK" ]; then
6+
RANK=$PMI_RANK
7+
elif [ -n "$OMPI_COMM_WORLD_RANK" ]; then
8+
RANK=$OMPI_COMM_WORLD_RANK
9+
elif [ -n "$SLURM_PROCID" ]; then
10+
RANK=$SLURM_PROCID
11+
else
12+
RANK=0
13+
fi
14+
15+
# Set unique profile file for each rank
16+
export LLVM_PROFILE_FILE="{llvm_profile_base}_rank_$RANK"
17+
exec "$@"

0 commit comments

Comments
 (0)