Skip to content

Commit 09bcdab

Browse files
committed
refactor run_tests: extend functionality with processes_coverage mode and improve docstring formatting for clarity
1 parent 87e8b8d commit 09bcdab

File tree

5 files changed

+113
-29
lines changed

5 files changed

+113
-29
lines changed

.github/workflows/ubuntu.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ jobs:
361361
run: |
362362
cmake --build build --parallel
363363
- name: Run tests (MPI)
364-
run: scripts/run_tests.py --running-type="processes"
364+
run: scripts/run_tests.py --running-type="processes_coverage"
365365
env:
366366
PPC_NUM_PROC: 2
367367
PPC_NUM_THREADS: 2
@@ -373,6 +373,7 @@ jobs:
373373
run: |
374374
mkdir cov-report
375375
cd build
376+
# Collect coverage data from all MPI rank directories and regular build directory
376377
gcovr -r ../ \
377378
--exclude '.*3rdparty/.*' \
378379
--exclude '/usr/.*' \
@@ -383,6 +384,12 @@ jobs:
383384
--exclude '.*modules/util/include/perf_test_util.hpp' \
384385
--exclude '.*modules/util/include/func_test_util.hpp' \
385386
--exclude '.*modules/util/src/func_test_util.cpp' \
387+
--gcov-ignore-parse-errors \
388+
--search-paths . \
389+
--search-paths gcov_data/rank_0 \
390+
--search-paths gcov_data/rank_1 \
391+
--search-paths gcov_data/rank_2 \
392+
--search-paths gcov_data/rank_3 \
386393
--xml --output ../coverage.xml \
387394
--html=../cov-report/index.html --html-details
388395
- name: Upload coverage reports to Codecov

modules/performance/tests/perf_tests.cpp

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -441,9 +441,8 @@ TEST(PerfTest, CommonRun_WithMultipleExecutions_CalculatesAverageTime) {
441441
if (call_count == 0) {
442442
call_count++;
443443
return 0.0; // Start time
444-
} else {
445-
return 3.0; // End time after 3 runs
446444
}
445+
return 3.0; // End time after 3 runs
447446
};
448447

449448
perf.PipelineRun(attr);
@@ -533,31 +532,31 @@ TEST(PerfTest, TaskRun_WithTiming_CompletesPipelineCorrectly) {
533532
// Create a custom task that counts method calls
534533
class CountingTask : public Task<int, int> {
535534
public:
536-
int* validation_count_;
537-
int* preprocessing_count_;
538-
int* run_count_;
539-
int* postprocessing_count_;
535+
int* validation_count;
536+
int* preprocessing_count;
537+
int* run_count;
538+
int* postprocessing_count;
540539

541540
CountingTask(int* vc, int* pc, int* rc, int* ppc)
542-
: validation_count_(vc), preprocessing_count_(pc), run_count_(rc), postprocessing_count_(ppc) {}
541+
: validation_count(vc), preprocessing_count(pc), run_count(rc), postprocessing_count(ppc) {}
543542

544543
bool ValidationImpl() override {
545-
(*validation_count_)++;
544+
(*validation_count)++;
546545
return true;
547546
}
548547

549548
bool PreProcessingImpl() override {
550-
(*preprocessing_count_)++;
549+
(*preprocessing_count)++;
551550
return true;
552551
}
553552

554553
bool RunImpl() override {
555-
(*run_count_)++;
554+
(*run_count)++;
556555
return true;
557556
}
558557

559558
bool PostProcessingImpl() override {
560-
(*postprocessing_count_)++;
559+
(*postprocessing_count)++;
561560
return true;
562561
}
563562
};

modules/task/include/task.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ class Task {
204204
if (stage_ != PipelineStage::kDone && stage_ != PipelineStage::kException) {
205205
// Immediate failure - better than global state pollution
206206
std::cerr << "[TASK ERROR] Task destroyed without completing pipeline. Stage: " << static_cast<int>(stage_)
207-
<< std::endl;
207+
<< '\n';
208208
terminate_handler_();
209209
}
210210
#if _OPENMP >= 201811

modules/task/tests/task_additional.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#include <gtest/gtest.h>
22

3-
#include <memory>
43
#include <string>
54
#include <utility>
65
#include <vector>

scripts/run_tests.py

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
#!/usr/bin/env python3
2+
"""
3+
Test runner script for a PPC project.
24
5+
This script provides functionality to run tests in different modes:
6+
- threads: for multithreading tests
7+
- processes: for multiprocessing tests
8+
- processes_coverage: for multiprocessing tests with coverage collection
9+
- performance: for performance testing
10+
"""
11+
12+
import argparse
313
import os
414
import shlex
515
import subprocess
@@ -8,13 +18,15 @@
818

919

1020
def init_cmd_args():
11-
import argparse
21+
"""Initialize and parse command line arguments."""
1222
parser = argparse.ArgumentParser()
1323
parser.add_argument(
1424
"--running-type",
1525
required=True,
16-
choices=["threads", "processes", "performance"],
17-
help="Specify the execution mode. Choose 'threads' for multithreading or 'processes' for multiprocessing."
26+
choices=["threads", "processes", "processes_coverage", "performance"],
27+
help="Specify the execution mode. Choose 'threads' for multithreading, "
28+
"'processes' for multiprocessing, 'processes_coverage' for multiprocessing "
29+
"with coverage, or 'performance' for performance testing."
1830
)
1931
parser.add_argument(
2032
"--additional-mpi-args",
@@ -34,6 +46,8 @@ def init_cmd_args():
3446

3547

3648
class PPCRunner:
49+
"""Runner class for PPC test execution in different modes."""
50+
3751
def __init__(self):
3852
self.__ppc_num_threads = None
3953
self.__ppc_num_proc = None
@@ -54,6 +68,7 @@ def __get_project_path():
5468
return script_dir.parent
5569

5670
def setup_env(self, ppc_env):
71+
"""Setup environment variables and working directory."""
5772
self.__ppc_env = ppc_env
5873

5974
self.__ppc_num_threads = self.__ppc_env.get("PPC_NUM_THREADS")
@@ -71,9 +86,9 @@ def setup_env(self, ppc_env):
7186
self.work_dir = Path(self.__get_project_path()) / "build" / "bin"
7287

7388
def __run_exec(self, command):
74-
result = subprocess.run(command, shell=False, env=self.__ppc_env)
89+
result = subprocess.run(command, shell=False, env=self.__ppc_env, check=False)
7590
if result.returncode != 0:
76-
raise Exception(f"Subprocess return {result.returncode}.")
91+
raise subprocess.CalledProcessError(result.returncode, command)
7792

7893
@staticmethod
7994
def __get_gtest_settings(repeats_count, type_task):
@@ -87,6 +102,7 @@ def __get_gtest_settings(repeats_count, type_task):
87102
return command
88103

89104
def run_threads(self):
105+
"""Run tests in threading mode."""
90106
if platform.system() == "Linux" and not self.__ppc_env.get("PPC_ASAN_RUN"):
91107
for task_type in ["seq", "stl"]:
92108
self.__run_exec(
@@ -97,10 +113,12 @@ def run_threads(self):
97113

98114
for task_type in ["omp", "seq", "stl", "tbb"]:
99115
self.__run_exec(
100-
[str(self.work_dir / 'ppc_func_tests')] + self.__get_gtest_settings(3, '_' + task_type + '_')
116+
[str(self.work_dir / 'ppc_func_tests')] +
117+
self.__get_gtest_settings(3, '_' + task_type + '_')
101118
)
102119

103120
def run_core(self):
121+
"""Run core functionality tests."""
104122
if platform.system() == "Linux" and not self.__ppc_env.get("PPC_ASAN_RUN"):
105123
self.__run_exec(
106124
shlex.split(self.valgrind_cmd)
@@ -114,6 +132,7 @@ def run_core(self):
114132
)
115133

116134
def run_processes(self, additional_mpi_args):
135+
"""Run tests in multiprocessing mode."""
117136
ppc_num_proc = self.__ppc_env.get("PPC_NUM_PROC")
118137
if ppc_num_proc is None:
119138
raise EnvironmentError("Required environment variable 'PPC_NUM_PROC' is not set.")
@@ -127,7 +146,63 @@ def run_processes(self, additional_mpi_args):
127146
+ self.__get_gtest_settings(10, '_' + task_type)
128147
)
129148

149+
def run_processes_coverage(self, additional_mpi_args):
150+
"""Run tests in multiprocessing mode with a coverage collection."""
151+
ppc_num_proc = self.__ppc_env.get("PPC_NUM_PROC")
152+
if ppc_num_proc is None:
153+
raise EnvironmentError("Required environment variable 'PPC_NUM_PROC' is not set.")
154+
155+
mpi_running = [self.mpi_exec] + shlex.split(additional_mpi_args) + ["-np", ppc_num_proc]
156+
157+
# Set up coverage environment for MPI processes
158+
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+
)
170+
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)
193+
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)
201+
)
202+
self.__run_exec(test_command)
203+
130204
def run_performance(self):
205+
"""Run performance tests."""
131206
if not self.__ppc_env.get("PPC_ASAN_RUN"):
132207
mpi_running = [self.mpi_exec, "-np", self.__ppc_num_proc]
133208
for task_type in ["all", "mpi"]:
@@ -139,25 +214,29 @@ def run_performance(self):
139214

140215
for task_type in ["omp", "seq", "stl", "tbb"]:
141216
self.__run_exec(
142-
[str(self.work_dir / 'ppc_perf_tests')] + self.__get_gtest_settings(1, '_' + task_type)
217+
[str(self.work_dir / 'ppc_perf_tests')] +
218+
self.__get_gtest_settings(1, '_' + task_type)
143219
)
144220

145221

146-
def _execute(args_dict, env):
222+
def _execute(args_dict_, env):
223+
"""Execute tests based on the provided arguments."""
147224
runner = PPCRunner()
148225
runner.setup_env(env)
149226

150-
if args_dict["running_type"] in ["threads", "processes"]:
227+
if args_dict_["running_type"] in ["threads", "processes", "processes_coverage"]:
151228
runner.run_core()
152229

153-
if args_dict["running_type"] == "threads":
230+
if args_dict_["running_type"] == "threads":
154231
runner.run_threads()
155-
elif args_dict["running_type"] == "processes":
156-
runner.run_processes(args_dict["additional_mpi_args"])
157-
elif args_dict["running_type"] == "performance":
232+
elif args_dict_["running_type"] == "processes":
233+
runner.run_processes(args_dict_["additional_mpi_args"])
234+
elif args_dict_["running_type"] == "processes_coverage":
235+
runner.run_processes_coverage(args_dict_["additional_mpi_args"])
236+
elif args_dict_["running_type"] == "performance":
158237
runner.run_performance()
159238
else:
160-
raise Exception("running-type is wrong!")
239+
raise ValueError(f"Invalid running-type: {args_dict_['running_type']}")
161240

162241

163242
if __name__ == "__main__":
@@ -171,7 +250,7 @@ def _execute(args_dict, env):
171250
if args_dict["running_type"] == "threads":
172251
env_copy["PPC_NUM_THREADS"] = str(count)
173252
env_copy.setdefault("PPC_NUM_PROC", "1")
174-
elif args_dict["running_type"] == "processes":
253+
elif args_dict["running_type"] in ["processes", "processes_coverage"]:
175254
env_copy["PPC_NUM_PROC"] = str(count)
176255
env_copy.setdefault("PPC_NUM_THREADS", "1")
177256

0 commit comments

Comments
 (0)