Skip to content

Commit 9e08029

Browse files
authored
Parallelise o2dpg generator tests (#2250)
* Implementation of parallel testing
1 parent bc61750 commit 9e08029

File tree

1 file changed

+88
-30
lines changed

1 file changed

+88
-30
lines changed

test/run_generator_tests.sh

Lines changed: 88 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -46,37 +46,35 @@ TEST_COUNTER=1
4646
# whether or not to delete everything except logs (default is to delete)
4747
KEEP_ONLY_LOGS=1
4848

49+
# Number of workers for parallel test runs
50+
JOBS=${JOBS:-8}
51+
echo "Running tests with up to ${JOBS} parallel jobs"
4952
# Prepare some colored output
5053
SRED="\033[0;31m"
5154
SGREEN="\033[0;32m"
5255
SEND="\033[0m"
5356

54-
5557
echo_green()
5658
{
5759
echo -e "${SGREEN}${*}${SEND}"
5860
}
5961

60-
6162
echo_red()
6263
{
6364
echo -e "${SRED}${*}${SEND}"
6465
}
6566

66-
6767
# Prevent the script from being soured to omit unexpected surprises when exit is used
6868
SCRIPT_NAME="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")"
6969
if [ "${SCRIPT_NAME}" != "$(basename ${BASH_SOURCE[0]})" ] ; then
7070
echo_red "This script cannot not be sourced" >&2
7171
return 1
7272
fi
7373

74-
7574
##################################
7675
# Core and utility functionality #
7776
##################################
7877

79-
8078
get_test_script_path_for_ini()
8179
{
8280
local ini_path=${1}
@@ -111,9 +109,10 @@ exec_test()
111109
local ini_path=${1}
112110
local generator=${2} # for now one of "Pythia8" or "External", at this point we know that settings for the generator are defined in this ini
113111
local generator_lower=$(echo "${generator}" | tr '[:upper:]' '[:lower:]')
112+
local test_id=${3} # DPL session ID
114113
# TODO Potentially, one could run an external generator that derives from GeneratorPythia8 and so could probably use configuration for TriggerPythia8
115-
local trigger=${3:+-t ${generator_lower}}
116-
local trigger_dpl=${3:+--trigger ${generator_lower}}
114+
local trigger=${4:+-t ${generator_lower}}
115+
local trigger_dpl=${4:+--trigger ${generator_lower}}
117116
local RET=0
118117
# this is how our test script is expected to be called
119118
local test_script=$(get_test_script_path_for_ini ${ini_path})
@@ -126,13 +125,14 @@ exec_test()
126125
echo "### Testing DPL-eventgen ###" >> ${LOG_FILE_SIM}
127126
# run the event generation using the dpl-eventgen executable.
128127
# This is a basic running test, however it's important because the system running on Hyperloop
129-
# is largely used for MCGEN productions and is currently tested only locally
130-
o2-sim-dpl-eventgen --generator ${generator_lower} ${trigger_dpl} --nEvents ${nev} --configFile ${ini_path} --configKeyValues "GeneratorPythia8.includePartonEvent=true" -b >> ${LOG_FILE_SIM} 2>&1
128+
# is largely used for MCGEN productions and is currently tested only locally.
129+
# Using unique session labels to prevent channel binding conflicts in parallel execution
130+
o2-sim-dpl-eventgen --session test_${test_id} --generator ${generator_lower} ${trigger_dpl} --nEvents ${nev} --configFile ${ini_path} --configKeyValues "GeneratorPythia8.includePartonEvent=true" -b >> ${LOG_FILE_SIM} 2>&1
131131
RET=${?}
132132
[[ "${RET}" != "0" ]] && { remove_artifacts ; return ${RET} ; }
133133
# run the simulation, fail if not successful
134134
echo "### Testing base o2-sim executable ###" >> ${LOG_FILE_SIM}
135-
o2-sim -g ${generator_lower} ${trigger} --noGeant -n ${nev} -j 4 --configFile ${ini_path} --configKeyValues "GeneratorPythia8.includePartonEvent=true" >> ${LOG_FILE_SIM} 2>&1
135+
o2-sim -g ${generator_lower} ${trigger} --noGeant -n ${nev} -j 1 --configFile ${ini_path} --configKeyValues "GeneratorPythia8.includePartonEvent=true" >> ${LOG_FILE_SIM} 2>&1
136136
RET=${?}
137137
[[ "${RET}" != "0" ]] && { remove_artifacts ; return ${RET} ; }
138138

@@ -151,6 +151,13 @@ exec_test()
151151
return ${RET}
152152
}
153153

154+
wait_for_slot()
155+
{
156+
# Wait until the number of background jobs is within the limit
157+
while (( $(jobs -r | wc -l) >= JOBS )) ; do
158+
sleep 0.1
159+
done
160+
}
154161

155162
check_generators()
156163
{
@@ -169,31 +176,37 @@ check_generators()
169176
local look_for=$(grep " ${g}.*\(\)" ${test_script})
170177
local has_trigger="$(grep Trigger${g} ${ini_path})"
171178
[[ -z "${look_for}" ]] && continue
172-
echo -n "Test ${TEST_COUNTER}: ${ini_path} with generator ${g}"
173179
tested_any=1
174180
# prepare the test directory
175181
local test_dir=${TEST_COUNTER}_$(basename ${ini})_${g}_dir
176182
rm -rf ${test_dir} 2> /dev/null
177183
mkdir ${test_dir}
178-
pushd ${test_dir} > /dev/null
179-
# one single test
180-
exec_test ${ini_path} ${g} ${has_trigger}
181-
RET=${?}
182-
popd > /dev/null
183-
if [[ "${RET}" != "0" ]] ; then
184-
echo_red " -> FAILED"
185-
ret_this=${RET}
186-
else
187-
echo_green " -> PASSED"
188-
fi
184+
local test_num=${TEST_COUNTER}
189185
((TEST_COUNTER++))
186+
187+
# Wait for an available slot before starting a new test
188+
wait_for_slot
189+
190+
echo "Test ${test_num}: ${ini_path} with generator ${g} - STARTED"
191+
# Run test in background
192+
(
193+
cd ${test_dir}
194+
exec_test ${ini_path} ${g} ${test_num} ${has_trigger}
195+
exit $?
196+
) &
197+
local pid=$!
198+
199+
# Store test information in global arrays
200+
test_pids+=(${pid})
201+
test_numbers+=(${test_num})
202+
test_generators+=(${g})
203+
test_ini_paths+=("${ini_path}")
190204
fi
191205
done
192206
[[ -z "${tested_any}" ]] && { echo_red "No test scenario was found for any generator. There must be at least one generator to be tested." ; ret_this=1 ; }
193207
return ${ret_this}
194208
}
195209

196-
197210
add_ini_files_from_macros()
198211
{
199212
# given a list of macros, collect all INI files which contain at least one of them
@@ -216,7 +229,6 @@ add_ini_files_from_macros()
216229
done
217230
}
218231

219-
220232
get_root_includes()
221233
{
222234
# check if some R__ADD_INCLUDE_PATH is used in the including macro and check the included file against that
@@ -236,7 +248,6 @@ get_root_includes()
236248
echo ${full_includes}
237249
}
238250

239-
240251
find_including_macros()
241252
{
242253
# figure out the macros that INCLUDE macros that have changed, so that in turn we can check
@@ -283,7 +294,6 @@ find_including_macros()
283294
echo ${including_macros}
284295
}
285296

286-
287297
add_ini_files_from_tests()
288298
{
289299
# Collect also those INI files for which the test has been changed
@@ -302,7 +312,6 @@ add_ini_files_from_tests()
302312
done
303313
}
304314

305-
306315
collect_ini_files()
307316
{
308317
# Collect all INI files which have changed
@@ -336,7 +345,6 @@ collect_ini_files()
336345
add_ini_files_from_tests ${macros}
337346
}
338347

339-
340348
get_git_repo_directory()
341349
{
342350
local repo=
@@ -381,11 +389,11 @@ print_usage()
381389

382390
# whether or not to exit after first test has failed
383391
fail_immediately=
384-
[[ "${1}" == "--fail_immediately" ]] && fail_immediately=1
392+
[[ "${1}" == "--fail-immediately" ]] && fail_immediately=1
385393

386394
while [ "$1" != "" ] ; do
387395
case $1 in
388-
--fail_immediately ) shift
396+
--fail-immediately ) shift
389397
fail_immediately=1
390398
;;
391399
--keep-artifacts ) shift
@@ -469,6 +477,12 @@ pushd ${TEST_PARENT_DIR} > /dev/null
469477
# global return code to be returned at the end
470478
ret_global=0
471479

480+
# Global arrays to track all test jobs (across all INI files)
481+
declare -a test_pids
482+
declare -a test_numbers
483+
declare -a test_generators
484+
declare -a test_ini_paths
485+
472486
# check each of the INI files
473487
for ini in ${ini_files_full_paths} ; do
474488

@@ -495,6 +509,50 @@ for ini in ${ini_files_full_paths} ; do
495509
[[ "${fail_immediately}" == "1" ]] && break
496510
fi
497511
done
512+
513+
# Wait for all test jobs to complete and collect results
514+
total_tests=${#test_pids[@]}
515+
completed=0
516+
while (( completed < total_tests )) ; do
517+
# Wait for any background job to complete
518+
wait -n
519+
RET=$?
520+
521+
# Find which job completed by checking which PID no longer exists
522+
for idx in "${!test_pids[@]}" ; do
523+
pid="${test_pids[$idx]}"
524+
if ! kill -0 ${pid} 2>/dev/null ; then
525+
# This job has completed, get its actual exit status
526+
wait ${pid} 2>/dev/null
527+
RET=$?
528+
529+
test_num="${test_numbers[$idx]}"
530+
generator="${test_generators[$idx]}"
531+
ini_path="${test_ini_paths[$idx]}"
532+
533+
if [[ "${RET}" != "0" ]] ; then
534+
echo_red "Test ${test_num}: ${ini_path} with generator ${generator} -> FAILED"
535+
ret_global=${RET}
536+
if [[ "${fail_immediately}" == "1" ]] ; then
537+
# Kill remaining background jobs
538+
for remaining_pid in "${test_pids[@]}" ; do
539+
kill ${remaining_pid} 2>/dev/null
540+
done
541+
completed=${total_tests}
542+
break
543+
fi
544+
else
545+
echo_green "Test ${test_num}: ${ini_path} with generator ${generator} -> PASSED"
546+
fi
547+
548+
# Remove this job from tracking
549+
unset test_pids[$idx]
550+
((completed++))
551+
break
552+
fi
553+
done
554+
done
555+
498556
# return to where we came from
499557
popd > /dev/null
500558

0 commit comments

Comments
 (0)