Newer
Older
#set -x
set -e
set -o pipefail #abort if left command on a pipe fails
function usage {
echo "Usage: $0 [-h] [--repo-user USER] [--repo-protocol PROTOCOL] [--repo-repo REPO] [--no-update] [--no-compil]"
echo " [--no-exec] [--no-comp] [--no-remove] [--force] [--commit SHA] [--ref REF]"
echo " [--only-model MODEL] [--no-enable-gh-pages] [--perf PERF] [--no-doc-gen]"
echo " [--hostname HOSTNAME] [MAIL]"
echo "--repo-user USER"
echo " user hosting the PHYEX repository on github,"
echo " defaults to the env variable PHYEXREPOuser (=$PHYEXREPOuser)"
echo "--repo-protocol PROTOCOL"
echo " protocol (https or ssh) to reach the PHYEX repository on github,"
echo " defaults to the env variable PHYEXREPOprotocol (=$PHYEXREPOprotocol)"
echo "--repo-repo REPO"
echo " repository name defaults to the env variable PHYEXREPOrepo (=$PHYEXREPOrepo)"
echo "--no-update do not update the tools"
echo "--no-compil do not compil (only usefull after a first execution with --no-update)"
echo "--no-exec do not execute (only usefull after a first execution with --no-update)"
echo "--no-comp do not compare (only usefull after a first execution with --no-update)"
echo "--no-remove do not remove compilation directory"

RIETTE Sébastien
committed
echo "--force perform the test even if github commit status already exists"
echo "--commit SHA use the commit with sha SHA instead of the last one"
echo "--ref REF ref to use (defaults to refs/heads/master)"
echo "--only-model MODEL"
echo " performs the test only using model MODEL (option can be provided several times)"

RIETTE Sébastien
committed
echo "--no-enable-gh-pages"
echo " dont't try to enable the project pages on github"
echo "--perf FILE add performance statistics in file FILE"

RIETTE Sébastien
committed
echo "--no-doc-gen do not test the documentation generation"
echo "--hostname HOSTNAME"
echo " the context is built using the provided hostname instead of the real hostname"
echo " This can be usefull when running on a cluster node which can vary from run to run"
echo "MAIL comma-separated list of e-mail addresses (no spaces); if not provided, mail is not sent"
echo ""
echo "This script provides functionality for automated tests."
echo "It can be run with cron to periodically test the last commit on the PHYEX repository"
echo "(eg '00 22 * * * bash -l -c \"SHELL=/bin/bash PHYEXWORKDIR=~/PHYEXTESTING ~/PHYEXTESTING/PHYEX/tools/testing.sh \\"
echo " --repo-user UMR-CNRM --repo-protocol ssh --repo-repo PHYEX user@domain\"')"

RIETTE Sébastien
committed
echo "The repository must be hosted on github as it relies on github project pages and github statuses."
echo "A github token must be set in the .netrc file."
echo ""
echo "All the work is done within the \${PHYEXWORKDIR} directory (it defaults to ~/PHYEXTESTING)."
echo "It may be necessary to fill the \${PHYEXWORKDIR}/PHYEX/tools/pack with base source code"
echo "(for arome and/or mesonh) or with data (testprogs)."
echo ""
echo "The script compare the results against reference simulations. These reference simulations must"

RIETTE Sébastien
committed
echo "be available in the different subdirectories in \${PHYEXWORKDIR}."
echo ""
echo "The documentation generation is also tested."
}
MAIL=""
PHYEXREPOuser=${PHYEXREPOuser:=UMR-CNRM}
PHYEXREPOrepo=${PHYEXREPOrepo:=PHYEX}
PHYEXREPOprotocol=${PHYEXREPOprotocol:=ssh}
REF="refs/heads/master"
WORKDIR=${PHYEXWORKDIR:=${HOME}/PHYEXTESTING}
update=1
compil=1
execute=1
comp=1
remove=1
commit=""
SHA=0
force=0
models=""

RIETTE Sébastien
committed
enableghpages=1

RIETTE Sébastien
committed
docgen=1
contextHostname=${HOSTNAME}
while [ -n "$1" ]; do
case "$1" in
'-h') usage; exit;;
'--repo-user') export PHYEXREPOuser=$2; shift;;
'--repo-protocol') export PHYEXREPOprotocol=$2; shift;;
'--repo-repo') export PHYEXREPOrepo=$2; shift;;
'--no-update') update=0;;
'--no-compil') compil=0;;
'--no-exec') execute=0;;
'--no-comp') comp=0;;
'--no-remove') remove=0;;
'--force') force=1;;
'--commit') SHA=$2; shift;;
'--ref') REF=$2; shift;;
'--only-model') models="${models} $2"; shift;;

RIETTE Sébastien
committed
'--no-enable-gh-pages') enableghpages=0;;
'--perf') perfopt="--perf $2"; shift;;

RIETTE Sébastien
committed
'--no-doc-gen') docgen=0;;
'--hostname') contextHostname=$2; shift;;
#--) shift; break ;;
*) if [ -z "${MAIL-}" ]; then
MAIL="$1"
else
echo "Only one email address allowed"
exit 1
fi;;
esac
shift
done
[ "${models}" == "" ] && models="ial mesonh testprogs lmdz"
[ ! -d ${WORKDIR} ] && mkdir -p ${WORKDIR}
#stdout and stderr redirection
logfile="${WORKDIR}/logfile"
if [ -f "${logfile}" ]; then
mv "${logfile}" "${logfile}.old"
fi
exec > "${logfile}" 2>&1

RIETTE Sébastien
committed
#context for statuses
context="continuous-integration/${contextHostname}"
#Interactions with github
if [ "${PHYEXREPOprotocol}" == 'ssh' ]; then
PHYEXREPOgiturl="git@github.com:${PHYEXREPOuser}/${PHYEXREPOrepo}.git"
else
PHYEXREPOgiturl="https://github.com/${PHYEXREPOuser}/${PHYEXREPOrepo}.git"
fi

RIETTE Sébastien
committed
TOKEN=$(python3 -c "import netrc, socket; print(netrc.netrc().authenticators('github.com')[2])")
function get_last_commit {
git ls-remote "${PHYEXREPOgiturl}" "${REF}" | cut -f1
}

RIETTE Sébastien
committed
function enable_gh_pages {
result=$(curl -L --netrc --insecure \

RIETTE Sébastien
committed
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${PHYEXREPOuser}/${PHYEXREPOrepo}/pages")
if [ $(echo $result | grep 'Not Found' | wc -l) -eq 1 ]; then
log 1 "Github project pages not yet enabled"
#Pages are not yet activated
curl -L --netrc --insecure \

RIETTE Sébastien
committed
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${PHYEXREPOuser}/${PHYEXREPOrepo}/pages" \
-d '{"source":{"branch":"master","path":"/docs"}}'
fi
}
function get_statuses {
curl -L --netrc --insecure \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \

RIETTE Sébastien
committed
"https://api.github.com/repos/${PHYEXREPOuser}/${PHYEXREPOrepo}/commits/${SHA}/statuses"

RIETTE Sébastien
committed
function add_status {
error=$1
ret=$2
SHA="$3"
comment="$4"
if [ $ret -eq 0 ]; then
state="success"
else
if [ $error -eq 1 ]; then
state="error"
else
state="failure"
fi
fi
url="https://${PHYEXREPOuser}.github.io/${PHYEXREPOrepo}/displayparam.html?"
url=${url}$(content=$(echo -e "$comment") python3 -c "import urllib.parse, os; print(urllib.parse.quote('<pre>' + os.environ['content'] + '</pre>', safe=':/='))")
curl -L --insecure \
-X POST \
-H "Accept: application/vnd.github+json" \

RIETTE Sébastien
committed
-H "Authorization: Bearer $TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \

RIETTE Sébastien
committed
"https://api.github.com/repos/${PHYEXREPOuser}/${PHYEXREPOrepo}/statuses/${SHA}" \
-d '{"state":"'${state}'","target_url":"'${url}'","context":"'${context}'"}'
}
function get_cases {
SHA="$1"
file="$2"
url="https://raw.githubusercontent.com/${PHYEXREPOuser}/${PHYEXREPOrepo}/${SHA}/${file}"
content=$(wget --no-check-certificate "${url}" -O - 2>/dev/null)
if [ "${content}" != "" ]; then
content="${content}" python3 -c "import json, os; print(' '.join([k+':'+v for k, v in json.loads(os.environ['content']).get('testing', {}).items()]))"
fi
}
#reporting
function send_mail {
message="$1"
if [ "$MAIL" != "" ]; then
if command -v mail; then
mail -s "$context" "$MAIL" <<EOF
$(echo -e ${message})
EOF
else
sendmail "$MAIL" <<EOF
Subject: $context
$(echo -e ${message})
EOF
fi

RIETTE Sébastien
committed
header="${context}\n\n$(date)"
message=""
function report {

RIETTE Sébastien
committed
error=$1
ret=$2
if [ ${ret} -eq 0 ]; then
error_msg=""
else
error_msg="XXXXXXXXXXXXXXXXXXXX ERROR ${ret} XXXXXXXXXXXXXXXXXXXX"
error_msg="${error_msg}\n\n"
fi
message="${header}\n${message}\n\n${error_msg}$(date)"
if [ ${ret} -ne 0 ]; then
send_mail "${message}"
fi
if [ "${SHA}" != 0 ]; then

RIETTE Sébastien
committed
add_status $error $ret "${SHA}" "${message}"
fi
}
log_message=""
function exit_error {
ret=$1
if [ ${ret} -ne 0 ]; then
message="__ ABNORMAL EXIT ${ret} __\n${log_message}\n${message}"
message="${message}\n\nMore information can be found in ${HOSTNAME}:${logfile}"

RIETTE Sébastien
committed
report 1 ${ret}
fi
}
trap 'exit_error $?' EXIT
function log {
level=$1; shift
echo "$@"
if [ ${level} -eq 0 ]; then
message="${message}\n$@"
fi
log_message="${log_message}\n$@"
}
#Test
if [ "${SHA}" -eq 0 ]; then
log 1 "Getting last commit hash"
SHA=$(get_last_commit)
log 1 "Commit hash is ${SHA}"
fi

RIETTE Sébastien
committed
if [ ${force} -eq 1 -o $(get_statuses "${SHA}" | grep "${context}" | wc -l) -eq 0 ]; then

RIETTE Sébastien
committed
log 1 "This commit has not been tested (or --force is provided)"
ret=0
#Checkout tools, set PATH and use the last version of the testing script
currentdir="${PWD}"
if [ ${update} -eq 1 ]; then
currentMD5=$(md5sum "${BASH_SOURCE[0]}" | cut -d\ -f1)
if [ ! -d "${WORKDIR}/PHYEX" ]; then
log 1 "Clonig PHYEX in ${WORKDIR}/PHYEX"
git clone "${PHYEXREPOgiturl}" "${WORKDIR}/PHYEX"
log 1 "Installing PHYEX"
./tools/INSTALL.sh --ALL
fi
log 1 "Checkout commit ${SHA}"
cd "${WORKDIR}/PHYEX"
git fetch "${PHYEXREPOgiturl}"
git checkout "${SHA}"
cd "${currentdir}"
if [ -f "${WORKDIR}/PHYEX/tools/testing.sh" ]; then
if [ "${currentMD5}" != $(md5sum "${WORKDIR}/PHYEX/tools/testing.sh" | cut -d\ -f1) ]; then
log 1 "Script has changed, running the new version" #This log and the previous ones are lost
exec "${WORKDIR}/PHYEX/tools/testing.sh" $allargs

RIETTE Sébastien
committed
. "${WORKDIR}/PHYEX/tools/env.sh"

RIETTE Sébastien
committed
#Enable the gihub project pages
if [ $enableghpages -eq 1 ]; then
log 1 "Test if github project pages are enabled"
enable_gh_pages
fi
export TESTPROGSDIR="${WORKDIR}/TESTPROGS"
export HOMEPACK="${WORKDIR}/pack"
export MNHPACK="${WORKDIR}/MesoNH"
export LMDZPACK="${WORKDIR}/LMDZ"
for d in "${TESTPROGSDIR}" "${HOMEPACK}" "${MNHPACK}" "${LMDZPACK}"; do
if [ ! -d "${d}" ]; then
log 1 "Creating directory ${d}"
mkdir -p "${d}"
fi
done
for model in $models; do
retmodel=0
log 0 "Tests for model ${model}"
#Model specific configuration
if [ "${model}" == 'ial' ]; then
compilation='-p -c'

RIETTE Sébastien
committed
comparison='-C --computeRefIfNeeded'
jsonfile="src/arome/ial_version.json"
docmp=1
elif [ "${model}" == 'lmdz' ]; then
compilation='-p -c --nofcm'
execution="-r --nofcm $perfopt"

RIETTE Sébastien
committed
comparison='-C'
jsonfile="src/${model}/${model}_version.json"
docmp=0
else

RIETTE Sébastien
committed
compilation='-p -c'

RIETTE Sébastien
committed
comparison='-C --computeRefIfNeeded'
jsonfile="src/${model}/${model}_version.json"
docmp=1
fi
#Commande
cmd="check_commit_${model}.sh --repo-user ${PHYEXREPOuser} --repo-protocol ${PHYEXREPOprotocol} ${SHA}"
#Compilation
result=0
if [ ${compil} -eq 1 ]; then
compilecmd="$cmd ${compilation}"
log 1 "Compilation with ${compilecmd}"
set +e
${compilecmd}
result=$?
set -e
if [ ${result} -ne 0 ]; then
retmodel=1
log 0 " ${model} compilation: error"
log 0 " ${model} compilation: OK"
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
fi
fi
#Execution and comparison
if [ ${result} -eq 0 ]; then
#Get the list of cases with associated references
cases=$(get_cases "${SHA}" "${jsonfile}")
[ "${cases}" == "" ] && cases="DEF:DEF"
for case_ref in ${cases}; do
case=$(echo "${case_ref}" | cut -d: -f1)
ref=$(echo "${case_ref}" | cut -d: -f2)
if [ "${case}" == "DEF" -a "${ref}" == "DEF" ]; then
casearg=""
refarg="REF"
casedescr="default case(s)"
log 1 "No cases found in ${jsonfile}, we only test the default cases"
else
casearg="-t ${case}"
refarg="${ref}"
casedescr="${case} (ref=${ref})"
log 1 "Testing case ${case} against reference ${ref}"
fi
result=0
if [ ${execute} -eq 1 ]; then
execcmd="$cmd ${execution} ${casearg}"
log 1 "Excution with ${execcmd}"
set +e
${execcmd}
result=$?
set -e
if [ ${result} -ne 0 ]; then
retmodel=1
log 0 " ${model} ${casedescr}: execution error"
else
log 0 " ${model} ${casedescr}: execution OK (but status not reliable)"
fi
fi
if [ ${result} -eq 0 -a ${docmp} -eq 1 -a ${comp} -eq 1 ]; then

RIETTE Sébastien
committed
compcmd="$cmd ${comparison} ${casearg} ${refarg}"
log 1 "Comparison with ${compcmd}"
set +e
${compcmd}
result=$?
set -e
if [ ${result} -ne 0 ]; then
retmodel=1
log 0 " ${model} ${casedescr}: comparison error"
else
log 0 " ${model} ${casedescr}: comparison OK"
fi
fi
done
#Cleaning
if [ ${remove} -eq 1 ]; then
cleancmd="${cmd} --remove"
log 1 "Cleaning with ${cleancmd}"
set +e
${cleancmd}
result=$?
set -e
if [ ${result} -ne 0 ]; then
retmodel=1
log 0 " ${model}: cleaning error"
else
log 0 " ${model}: cleaning OK"
fi
fi
fi
if [ $retmodel -eq 0 ]; then
log 0 "..... global result for model $model: OK"
else
ret=1
log 0 "XXXXX global result for model $model: ERROR"
fi

RIETTE Sébastien
committed
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
if [ $docgen -eq 1 ]; then
retdoc=0
log 0 "Test doc generation"
doccmd="generate_standalone_doc.sh ${WORKDIR}/documentation.html"
log 1 "Doc generation with ${doccmd}"
set +e
${doccmd}
result=$?
set -e
if [ ${result} -ne 0 ]; then
retdoc=1
log 0 " doc generarion with generate_standalone_doc.sh: error"
else
log 0 " doc generarion with generate_standalone_doc.sh: OK"
fi
doccmd="doxygen doxygen_config"
log 1 "Doc generation with ${doccmd}"
cwd=$PWD
cd ${WORKDIR}/PHYEX/docs/doxygen
set +e
${doccmd}
result=$?
set -e
cd $cwd
if [ ${result} -ne 0 ]; then
retdoc=1
log 0 " doc generarion with doxygen: error"
else
log 0 " doc generarion with doxygen: OK"
fi
if [ $retdoc -eq 0 ]; then
log 0 "..... global result for doc generation: OK"
else
ret=1
log 0 "XXXXX global result for doc generation: ERROR"
fi
fi

RIETTE Sébastien
committed
report 0 ${ret}