basistest-master.sh
Go to the documentation of this file.
00001 #! /bin/bash 00002 __FILE__="$(cd -P -- "$(dirname -- "$BASH_SOURCE")" && pwd -P)/$(basename -- "$BASH_SOURCE")"; if [[ -n "$SGE_ROOT" ]] && [[ $__FILE__ =~ $SGE_ROOT/.* ]] && [[ -n "${BASIS_DIR}" ]] && [[ -f "${BASIS_DIR}/bin/basistest-master.sh" ]]; then __FILE__="${BASIS_DIR}/bin/basistest-master.sh"; fi; i=0; lnk="$__FILE__"; while [[ -h "$lnk" ]] && [[ $i -lt 100 ]]; do dir=`dirname -- "$lnk"`; lnk=`readlink -- "$lnk"`; lnk=`cd "$dir" && cd $(dirname -- "$lnk") && pwd`/`basename -- "$lnk"`; let i++; done; [[ $i -lt 100 ]] && __FILE__="$lnk"; unset -v i dir lnk; __DIR__="$(dirname -- "$__FILE__")"; BASIS_BASH_UTILITIES="$__DIR__/bash/basis/basis.sh"; BASHPATH="$__DIR__/.:$__DIR__/bash:$BASHPATH" # <-- added by BASIS 00003 ############################################################################## 00004 # @file basistest-master.sh 00005 # @brief Test master which can be run as a cron job. 00006 # 00007 # This shell script is supposed to be scheduled as cron job, where possibly 00008 # the basistest-cron.sh script is in fact used as cron job command without 00009 # arguments where all the settings for the cron job are fixed within this 00010 # latter script. On execution, this master script parses the configuration 00011 # file and executes the configured tests using by default the 00012 # basistest-slave.sh script. 00013 # 00014 # Copyright (c) 2011 University of Pennsylvania. All rights reserved.<br /> 00015 # See https://www.cbica.upenn.edu/sbia/software/license.html or COPYING file. 00016 # 00017 # Contact: SBIA Group <sbia-software at uphs.upenn.edu> 00018 # 00019 # @ingroup Tools 00020 ############################################################################## 00021 00022 . ${BASIS_BASH_UTILITIES} || exit 1 00023 00024 # ============================================================================ 00025 # constants 00026 # ============================================================================ 00027 00028 exedir _EXEC_DIR && readonly _EXEC_DIR 00029 exename _EXEC_NAME && readonly _EXEC_NAME 00030 00031 # ============================================================================ 00032 # default settings 00033 # ============================================================================ 00034 00035 # absolute path of tests configuration file 00036 conf_file='/etc/basistest.conf' 00037 00038 # absolute path of file with timestamps for next test execution 00039 schedule_file='/var/run/basistest.schedule' 00040 00041 # ============================================================================ 00042 # help/version 00043 # ============================================================================ 00044 00045 # ---------------------------------------------------------------------------- 00046 ## @brief Print documentation of options. 00047 # 00048 # @returns Nothing. 00049 function print_options 00050 { 00051 cat - << EOF-OPTIONS 00052 Options: 00053 -c [ --conf ] The test configuration file. Defaults to "${conf_file}". 00054 -t [ --testcmd ] The test execution command. Defaults to the basistest 00055 command in the same directory as this executable. 00056 -s [ --schedule ] The test schedule file which is created and updated by 00057 this program. Defaults to "${schedule_file}". 00058 --dry Dry run, i.e., do not actually invoke the test execution command. 00059 -v [ --verbose ] Increases verbosity of output messages. Can be given multiple times. 00060 -h [ --help ] Print help and exit. 00061 -u [ --usage ] Print short help and exit. 00062 -V [ --version ] Print version information and exit. 00063 EOF-OPTIONS 00064 } 00065 00066 # ---------------------------------------------------------------------------- 00067 ## @brief Print help. 00068 # 00069 # @returns Nothing. 00070 function print_help 00071 { 00072 echo "Usage:" 00073 echo " ${_EXEC_NAME} [options]" 00074 echo 00075 cat - << EOF-DESCRIPTION 00076 Description: 00077 This so-called testing master script is executed by the basistest-cron command. 00078 On each run, it reads in the configuration file given by the --config option 00079 line-by-line. Each line in the configuration file specifies one test job to be 00080 executed. See the next section for details on the format and content of such 00081 configuration file. 00082 00083 Configuration: 00084 The format of the configuration file is detailed here. Comments within the 00085 configuration file start with a '#' character at the beginning of each line. 00086 00087 For each test of a specific branch of a project, the configuration file 00088 contains a line following the format: 00089 00090 <m> <h> <d> <project> <branch> <model> <options> 00091 00092 where 00093 00094 <m> Interval in minutes between consecutive test runs. 00095 Defaults to "0" if "*" is given. 00096 <h> Interval in hours between consecutive test runs. 00097 Defaults to "0" if "*" is given. 00098 <d> Interval in days (i.e., multiples of 24 hours) between consecutive 00099 test runs. Defaults to "0" if "*" is given. 00100 <project> Name of the BASIS project. 00101 <branch> Branch within the project's SVN repository, e.g., "tags/1.0.0". 00102 Defaults to "trunk" if a "*" is given. 00103 <model> Dashboard model, i.e., either one of "Nightly", "Continuous", 00104 and "Experimental". Defaults to "Nightly". 00105 <options> Additional options to the CTest script. 00106 The "basistest.ctest" script of BASIS is used by default. 00107 Run "ctest -S <path>/basistest.ctest,usage" to get a list of 00108 available options. By default, the default options of the 00109 CTest script are used. Note that this option can in particular 00110 be used to define CMake variables for the build configuration. 00111 00112 Attention: Neither of these entries may contain any whitespace character! 00113 00114 For example, nightly tests of the main development branch (trunk) of the 00115 project BASIS itself which are run once every day including coverage 00116 analysis and memory checks are scheduled by 00117 00118 * * 1 BASIS trunk Nightly coverage,memcheck 00119 00120 Test execution: 00121 By default, the basistest-slave command is invoked for each entry in the 00122 configuration file. A custom test command can be set using the option --testcmd. 00123 The provided command has to support the following command line arguments. 00124 00125 --project <arg> The name of the project as given in the configuration. 00126 --branch <arg> The branch as given in the configuration. 00127 --model <arg> The name of the model as given in the configuration. 00128 --args <arg> The additional options given in the configuration. 00129 --verbose Enable verbose output messages. May be given multiple times. 00130 00131 The --args and --verbose options have to be optional. 00132 EOF-DESCRIPTION 00133 echo 00134 print_options 00135 echo 00136 cat - << EOF-EXAMPLES 00137 Examples: 00138 ${_EXEC_NAME} --conf /etc/basis/testd.conf --schedule /var/run/basis/testd 00139 00140 Runs this daemon with the configuration file "/etc/basis/testd.conf", 00141 where the test schedule "/var/run/basis/testd" is created (or updated). 00142 Note that this command should be setup as cron job instead of executing 00143 it manually. 00144 EOF-EXAMPLES 00145 echo 00146 print_contact 00147 } 00148 00149 # ---------------------------------------------------------------------------- 00150 ## @brief Print usage (i.e., only usage and options). 00151 # 00152 # @returns Nothing. 00153 function print_helpshort 00154 { 00155 echo "Usage:" 00156 echo " ${_EXEC_NAME} [options]" 00157 echo 00158 print_options 00159 echo 00160 print_contact 00161 } 00162 00163 # ============================================================================ 00164 # helpers 00165 # ============================================================================ 00166 00167 # ---------------------------------------------------------------------------- 00168 ## @brief Runs a test given the arguments in the configuration file. 00169 # 00170 # @param [in] project Name of the project to test. 00171 # @param [in] branch Name of the branch to test. 00172 # @param [in] model Name of the Dashboard model. 00173 # @param [in] options Additional options for the CTest script. 00174 # 00175 # @returns Whether the execution of the test was successful. 00176 # 00177 # @retval 0 On success. 00178 # @retval 1 On failure. 00179 function run_test 00180 { 00181 cmd="${test_cmd}" 00182 if [ ${verbose} -gt 1 ]; then cmd="${cmd} --verbose"; fi 00183 if [ ${verbose} -gt 2 ]; then cmd="${cmd} --verbose"; fi 00184 cmd="${cmd} --project $1 --branch $2 --model $3" 00185 if [ ! -z "$4" ]; then cmd="${cmd} --args $4"; fi 00186 if [ ${verbose} -gt 0 ]; then 00187 echo "$ ${cmd}" 00188 fi 00189 if [ "${dry}" == 'false' ]; then 00190 if [ ${verbose} -gt 0 ]; then 00191 ${cmd} 00192 else 00193 ${cmd} > /dev/null # avoid messages such as "Your job has been submitted" 00194 fi 00195 return $? 00196 fi 00197 return 0 00198 } 00199 00200 # ---------------------------------------------------------------------------- 00201 ## @brief Convert date to timestamp. 00202 # 00203 # @param [in] date Date. 00204 # 00205 # @returns Prints timestamp corresponding to given date to @c STDOUT. 00206 function date2stamp 00207 { 00208 if [ $(uname) == 'Darwin' ]; then 00209 date -j -f '%Y-%m-%d %T' "$1" +%s 00210 else 00211 date -d "$1" +%s 00212 fi 00213 } 00214 00215 # ---------------------------------------------------------------------------- 00216 ## @brief Convert timestamp to date. 00217 # 00218 # @param [in] stamp Timestamp. 00219 # 00220 # @return Prints date corresponding to given timestamp to @c STDOUT. 00221 function stamp2date 00222 { 00223 if [ $(uname) == 'Darwin' ]; then 00224 date -j -r $1 '+%Y-%m-%d %T' 00225 else 00226 date -d "1970-01-01 $1 sec UTC" '+%Y-%m-%d %T' 00227 fi 00228 } 00229 00230 # ---------------------------------------------------------------------------- 00231 ## @brief Adds a certain time interval to a given date. 00232 # 00233 # @param [in] unit Unit of the time interval. Either one of -s, -m, -h, or -d. 00234 # Defaults to number of days. 00235 # @param [in] date The date to which the time interval is added. 00236 # @param [in] interval The time interval given in the specified units. 00237 # 00238 # @returns Prints the date which is @p interval time units after the given 00239 # date to @c STDOUT. 00240 function date_add 00241 { 00242 case $1 in 00243 -s) sec=1; shift;; 00244 -m) sec=60; shift;; 00245 -h) sec=3600; shift;; 00246 -d) sec=86400; shift;; 00247 *) sec=86400;; 00248 esac 00249 local dte1=$(date2stamp "$1") 00250 local interval=$2 00251 local add_sec=$((dte1 + interval * sec)) 00252 echo $(stamp2date "${add_sec}") 00253 } 00254 00255 # ---------------------------------------------------------------------------- 00256 ## @brief Computes the time interval between two given dates. 00257 # 00258 # @param [in] unit Unit of the time interval. Either one of -s, -m, -h, or -d. 00259 # Defaults to number of days. 00260 # @param [in] date1 The first date. 00261 # @param [in] date2 The second date. 00262 # 00263 # @return Prints time interval, i.e., an absolute value, in the given units 00264 # to @c STDOUT. 00265 function date_diff 00266 { 00267 case $1 in 00268 -s) sec=1; shift;; 00269 -m) sec=60; shift;; 00270 -h) sec=3600; shift;; 00271 -d) sec=86400; shift;; 00272 *) sec=86400;; 00273 esac 00274 local dte1=$(date2stamp "$1") 00275 local dte2=$(date2stamp "$2") 00276 local interval=$((dte2 - dte1)) 00277 echo $((interval / sec)) 00278 } 00279 00280 # ---------------------------------------------------------------------------- 00281 ## @brief Get next scheduled date of a given test. 00282 # 00283 # @returns Prints date to @c STDOUT. 00284 function schedule_date 00285 { 00286 local retval=$(date '+%Y-%m-%d %T') 00287 idx=0 00288 numtests=${#schedule[@]} 00289 while [ ${idx} -lt ${numtests} ]; do 00290 parts=(${schedule[${idx}]}) 00291 numparts=${#parts[@]} 00292 if [ ${numparts} -lt 5 -o ${numparts} -gt 6 ]; then 00293 continue 00294 fi 00295 if [ "${parts[2]}" == "$1" \ 00296 -a "${parts[3]}" == "$2" \ 00297 -a "${parts[4]}" == "$3" \ 00298 -a "${parts[5]}" == "$4" ] 00299 then 00300 retval="${parts[0]} ${parts[1]}" 00301 fi 00302 ((idx++)) 00303 done 00304 echo "${retval}" 00305 } 00306 00307 # ---------------------------------------------------------------------------- 00308 ## @brief Add entry to test schedule. 00309 # 00310 # @param [in] date The date at which the test should be run next. 00311 # @param [in] time The time at which the test should be run next. 00312 # @param [in] project Name of the project. 00313 # @param [in] branch Name of the branch. 00314 # @param [in] model Name of the model. 00315 # 00316 # @returns Nothing. 00317 function schedule_test 00318 { 00319 idx=${#new_schedule[@]} 00320 new_schedule[${idx}]="$1 $2 $3 $4 $5" 00321 } 00322 00323 # ============================================================================ 00324 # options 00325 # ============================================================================ 00326 00327 test_cmd="${_EXEC_DIR}/basistest-slave" # command used to run tests 00328 verbose=0 # verbosity of output messages 00329 dry='false' # whether this is a dry testing run 00330 00331 while [ $# -gt 0 ]; do 00332 case "$1" in 00333 -c|--conf) 00334 shift 00335 if [ $# -gt 0 ]; then 00336 conf_file=$1 00337 else 00338 echo "Option --conf requires an argument!" 1>&2 00339 exit 1 00340 fi 00341 ;; 00342 -t|--testcmd) 00343 shift 00344 if [ $# -gt 0 ]; then 00345 test_cmd=$1 00346 else 00347 echo "Option --testcmd requires an argument!" 1>&2 00348 exit 1 00349 fi 00350 ;; 00351 -s|--schedule) 00352 shift 00353 if [ $# -gt 0 ]; then 00354 schedule_file=$1 00355 else 00356 echo "Option --schedule requires an argument!" 1>&2 00357 exit 1 00358 fi 00359 ;; 00360 --dry) dry='true'; ;; 00361 00362 # standard options 00363 -h|--help) print_help; exit 0; ;; 00364 -u|--usage) print_helpshort; exit 0; ;; 00365 -V|--version) print_version ""; exit 0; ;; 00366 -v|--verbose) (( verbose++ )); ;; 00367 00368 # invalid option 00369 *) 00370 print_helpshort 00371 echo 00372 echo "Invalid option $1!" 1>&2 00373 exit 1 00374 ;; 00375 esac 00376 shift 00377 done 00378 00379 # ============================================================================ 00380 # main 00381 # ============================================================================ 00382 00383 # check existence of configuration file 00384 if [ ! -f "${conf_file}" ]; then 00385 echo "Missing configuration file \"${conf_file}\"" 1>&2 00386 exit 1 00387 fi 00388 00389 # parse existing test schedule 00390 schedule=() 00391 new_schedule=() 00392 if [ -f "${schedule_file}" ]; then 00393 idx=0 00394 while read line; do 00395 schedule[${idx}]=${line} 00396 ((idx++)) 00397 done < ${schedule_file} 00398 fi 00399 00400 # variables set by readConfLine () which store the configuration for a 00401 # particular test run 00402 minutes=0 00403 hours=0 00404 days=0 00405 project='' 00406 branch='' 00407 model='' 00408 options='' 00409 00410 # read configuration file line by line 00411 linenumber=0 00412 errors=0 00413 while read line; do 00414 ((linenumber++)) 00415 # skip empty lines 00416 if [ -z "${line}" ]; then continue; fi 00417 # skip comments 00418 if [[ "${line}" =~ "^#" ]]; then continue; fi 00419 # sanitize line 00420 line=${line//\*/x} 00421 # "parse" line 00422 parts=(${line}) 00423 num=${#parts[@]} 00424 if [ ${num} -lt 4 ]; then 00425 echo "${conf_file}:${linenumber}: Invalid configuration, skipping test" 1>&2 00426 (( errors++ )) 00427 continue 00428 fi 00429 minutes=${parts[0]} 00430 hours=${parts[1]} 00431 days=${parts[2]} 00432 project=${parts[3]} 00433 branch=${parts[4]} 00434 model=${parts[5]} 00435 options=${parts[6]} 00436 # check arguments 00437 if [ -z "${minutes}" -o -z "${hours}" -o -z "${days}" ]; then 00438 echo "${conf_file}:${linenumber}: Invalid configuration, skipping test" 1>&2 00439 (( errors++ )) 00440 continue 00441 fi 00442 if [ "${minutes}" == "x" ]; then minutes=0; fi 00443 if [ "${hours}" == "x" ]; then hours=0; fi 00444 if [ "${days}" == "x" ]; then days=0; fi 00445 if [ ${minutes} -eq 0 -a ${hours} -eq 0 -a ${days} -eq 0 ]; then 00446 echo "${conf_file}:${linenumber}: Invalid test interval, skipping test" 1>&2 00447 (( errors++ )) 00448 continue 00449 fi 00450 if [ -z "${project}" ]; then 00451 echo "${conf_file}:${linenumber}: No project name given, skipping test" 1>&2 00452 (( errors++ )) 00453 continue 00454 fi 00455 if [ -z "${branch}" ]; then 00456 branch='trunk' 00457 fi 00458 if [ -z "${model}" ]; then 00459 model='Nightly' 00460 fi 00461 # determine whether test is already due for execution 00462 next_date=$(schedule_date ${project} ${branch} ${model} ${options}) 00463 if [ $(date_diff -m "$(date '+%Y-%m-%d %T')" "${next_date}") -gt 0 ]; then 00464 if [ ${verbose} -gt 0 ]; then 00465 echo "Next ${model} test of ${project} (${branch}) with options \"${options}\" is scheduled for ${next_date}" 00466 fi 00467 # skip test as it is not yet scheduled for execution 00468 schedule_test "${next_date}" "${project}" "${branch}" "${model}" "${options}" 00469 continue 00470 fi 00471 # run test 00472 run_test "${project}" "${branch}" "${model}" "${options}" 00473 if [ $? -ne 0 ]; then 00474 echo "${conf_file}:${linenumber}: Failed to run test" 1>&2 00475 (( errors++ )) 00476 # do not retry failing test too often 00477 minutes=0 00478 hours=1 00479 days=0 00480 fi 00481 # update time of next execution 00482 minutes=$((minutes + hours * 60 + days * 1440)) 00483 next_date=$(date_add -m "$(date '+%Y-%m-%d %T')" "${minutes}") 00484 schedule_test "${next_date}" "${project}" "${branch}" "${model}" "${options}" 00485 if [ $? -ne 0 ]; then 00486 echo "${conf_file}:${linenumber}: Failed to reschedule test" 1>&2 00487 (( errors++ )) 00488 fi 00489 if [ ${verbose} -gt 0 ]; then 00490 echo "Test will re-execute in ${minutes} minutes from now ($(date '+%Y-%m-%d %T')), i.e., not before ${next_date}" 00491 fi 00492 done < "${conf_file}" 00493 00494 # write new schedule to temporary file 00495 idx=0 00496 num=${#new_schedule[@]} 00497 if [ -f "${schedule_file}.temp" ]; then 00498 rm -f "${schedule_file}.temp" 00499 fi 00500 while [ ${idx} -lt ${num} ]; do 00501 echo ${new_schedule[${idx}]} >> "${schedule_file}.temp" 00502 if [ $? -ne 0 ]; then 00503 echo "Failed to write schedule to temporary file \"${schedule_file}.temp\"!" 1>&2 00504 exit 1; 00505 fi 00506 (( idx++ )) 00507 done 00508 # sort schedule 00509 if [ -f "${schedule_file}.temp" ]; then 00510 sort "${schedule_file}.temp" -o "${schedule_file}.temp" 00511 if [ $? -ne 0 ]; then 00512 echo "Failed to sort temporary schedule file \"${schedule_file}.temp\"!" 1>&2 00513 exit 1 00514 fi 00515 fi 00516 # and then replace previous schedule file 00517 if [ -f "${schedule_file}.temp" ]; then 00518 mv -f "${schedule_file}.temp" "${schedule_file}" 00519 if [ $? -ne 0 ]; then 00520 echo "Failed to update schedule file \"${schedule_file}\"!" 1>&2 00521 exit 1 00522 fi 00523 fi 00524 00525 # done 00526 [ ${errors} -eq 0 ]