1
1
#!/usr/bin/env python3
2
+ """
3
+ Test runner script for a PPC project.
2
4
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
3
13
import os
4
14
import shlex
5
15
import subprocess
8
18
9
19
10
20
def init_cmd_args ():
11
- import argparse
21
+ """Initialize and parse command line arguments."""
12
22
parser = argparse .ArgumentParser ()
13
23
parser .add_argument (
14
24
"--running-type" ,
15
25
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."
18
30
)
19
31
parser .add_argument (
20
32
"--additional-mpi-args" ,
@@ -34,6 +46,8 @@ def init_cmd_args():
34
46
35
47
36
48
class PPCRunner :
49
+ """Runner class for PPC test execution in different modes."""
50
+
37
51
def __init__ (self ):
38
52
self .__ppc_num_threads = None
39
53
self .__ppc_num_proc = None
@@ -54,6 +68,7 @@ def __get_project_path():
54
68
return script_dir .parent
55
69
56
70
def setup_env (self , ppc_env ):
71
+ """Setup environment variables and working directory."""
57
72
self .__ppc_env = ppc_env
58
73
59
74
self .__ppc_num_threads = self .__ppc_env .get ("PPC_NUM_THREADS" )
@@ -71,9 +86,9 @@ def setup_env(self, ppc_env):
71
86
self .work_dir = Path (self .__get_project_path ()) / "build" / "bin"
72
87
73
88
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 )
75
90
if result .returncode != 0 :
76
- raise Exception ( f"Subprocess return { result .returncode } ." )
91
+ raise subprocess . CalledProcessError ( result .returncode , command )
77
92
78
93
@staticmethod
79
94
def __get_gtest_settings (repeats_count , type_task ):
@@ -87,6 +102,7 @@ def __get_gtest_settings(repeats_count, type_task):
87
102
return command
88
103
89
104
def run_threads (self ):
105
+ """Run tests in threading mode."""
90
106
if platform .system () == "Linux" and not self .__ppc_env .get ("PPC_ASAN_RUN" ):
91
107
for task_type in ["seq" , "stl" ]:
92
108
self .__run_exec (
@@ -97,10 +113,12 @@ def run_threads(self):
97
113
98
114
for task_type in ["omp" , "seq" , "stl" , "tbb" ]:
99
115
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 + '_' )
101
118
)
102
119
103
120
def run_core (self ):
121
+ """Run core functionality tests."""
104
122
if platform .system () == "Linux" and not self .__ppc_env .get ("PPC_ASAN_RUN" ):
105
123
self .__run_exec (
106
124
shlex .split (self .valgrind_cmd )
@@ -114,6 +132,7 @@ def run_core(self):
114
132
)
115
133
116
134
def run_processes (self , additional_mpi_args ):
135
+ """Run tests in multiprocessing mode."""
117
136
ppc_num_proc = self .__ppc_env .get ("PPC_NUM_PROC" )
118
137
if ppc_num_proc is None :
119
138
raise EnvironmentError ("Required environment variable 'PPC_NUM_PROC' is not set." )
@@ -127,7 +146,63 @@ def run_processes(self, additional_mpi_args):
127
146
+ self .__get_gtest_settings (10 , '_' + task_type )
128
147
)
129
148
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
+
130
204
def run_performance (self ):
205
+ """Run performance tests."""
131
206
if not self .__ppc_env .get ("PPC_ASAN_RUN" ):
132
207
mpi_running = [self .mpi_exec , "-np" , self .__ppc_num_proc ]
133
208
for task_type in ["all" , "mpi" ]:
@@ -139,25 +214,29 @@ def run_performance(self):
139
214
140
215
for task_type in ["omp" , "seq" , "stl" , "tbb" ]:
141
216
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 )
143
219
)
144
220
145
221
146
- def _execute (args_dict , env ):
222
+ def _execute (args_dict_ , env ):
223
+ """Execute tests based on the provided arguments."""
147
224
runner = PPCRunner ()
148
225
runner .setup_env (env )
149
226
150
- if args_dict ["running_type" ] in ["threads" , "processes" ]:
227
+ if args_dict_ ["running_type" ] in ["threads" , "processes" , "processes_coverage " ]:
151
228
runner .run_core ()
152
229
153
- if args_dict ["running_type" ] == "threads" :
230
+ if args_dict_ ["running_type" ] == "threads" :
154
231
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" :
158
237
runner .run_performance ()
159
238
else :
160
- raise Exception ( " running-type is wrong! " )
239
+ raise ValueError ( f"Invalid running-type: { args_dict_ [ 'running_type' ] } " )
161
240
162
241
163
242
if __name__ == "__main__" :
@@ -171,7 +250,7 @@ def _execute(args_dict, env):
171
250
if args_dict ["running_type" ] == "threads" :
172
251
env_copy ["PPC_NUM_THREADS" ] = str (count )
173
252
env_copy .setdefault ("PPC_NUM_PROC" , "1" )
174
- elif args_dict ["running_type" ] == "processes" :
253
+ elif args_dict ["running_type" ] in [ "processes" , "processes_coverage" ] :
175
254
env_copy ["PPC_NUM_PROC" ] = str (count )
176
255
env_copy .setdefault ("PPC_NUM_THREADS" , "1" )
177
256
0 commit comments