@@ -46,37 +46,35 @@ TEST_COUNTER=1
4646# whether or not to delete everything except logs (default is to delete)
4747KEEP_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
5053SRED=" \033[0;31m"
5154SGREEN=" \033[0;32m"
5255SEND=" \033[0m"
5356
54-
5557echo_green ()
5658{
5759 echo -e " ${SGREEN}${* }${SEND} "
5860}
5961
60-
6162echo_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
6868SCRIPT_NAME=" $( basename " $( test -L " $0 " && readlink " $0 " || echo " $0 " ) " ) "
6969if [ " ${SCRIPT_NAME} " != " $( basename ${BASH_SOURCE[0]} ) " ] ; then
7070 echo_red " This script cannot not be sourced" >&2
7171 return 1
7272fi
7373
74-
7574# #################################
7675# Core and utility functionality #
7776# #################################
7877
79-
8078get_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
155162check_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-
197210add_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-
220232get_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-
240251find_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-
287297add_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-
306315collect_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-
340348get_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
383391fail_immediately=
384- [[ " ${1} " == " --fail_immediately " ]] && fail_immediately=1
392+ [[ " ${1} " == " --fail-immediately " ]] && fail_immediately=1
385393
386394while [ " $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
470478ret_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
473487for 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
497511done
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
499557popd > /dev/null
500558
0 commit comments