| #!/usr/bin/env bash |
| ### Copyright 2010 Manuel Carrasco MoƱino. (manolo at apache.org) |
| ### |
| ### Licensed under the Apache License, Version 2.0. |
| ### You may obtain a copy of it at |
| ### http://www.apache.org/licenses/LICENSE-2.0 |
| |
| ### |
| ### A library for shell scripts which creates reports in jUnit format. |
| ### These reports can be used in Jenkins, or any other CI. |
| ### |
| ### Usage: |
| ### - Include this file in your shell script |
| ### - Use juLog to call your command any time you want to produce a new report |
| ### Usage: juLog <options> command arguments |
| ### options: |
| ### -class="MyClass" : a class name which will be shown in the junit report |
| ### -name="TestName" : the test name which will be shown in the junit report |
| ### -error="RegExp" : a regexp which sets the test as failure when the output matches it |
| ### -ierror="RegExp" : same as -error but case insensitive |
| ### -output="Path" : path to output directory, defaults to "./results" |
| ### - Junit reports are left in the folder 'result' under the directory where the script is executed. |
| ### - Configure Jenkins to parse junit files from the generated folder |
| ### |
| |
| asserts=00; errors=0; total=0; content="" |
| date=`which gdate 2>/dev/null || which date` |
| |
| # default output folder |
| juDIR=`pwd`/results |
| |
| # The name of the suite is calculated based in your script name |
| suite="" |
| |
| if LANG=C sed --help 2>&1 | grep -q GNU; then |
| SED="sed" |
| elif which gsed &>/dev/null; then |
| SED="gsed" |
| else |
| echo "Failed to find GNU sed as sed or gsed. If you are on Mac: brew install gnu-sed." >&2 |
| exit 1 |
| fi |
| |
| # A wrapper for the eval method witch allows catching seg-faults and use tee |
| errfile=/tmp/evErr.$$.log |
| function eVal() { |
| (eval "$1") |
| # stdout and stderr may currently be inverted (see below) so echo may write to stderr |
| echo "$?" 2>&1 | tr -d "\n" > "${errfile}" |
| } |
| |
| # Method to clean old tests |
| function juLogClean() { |
| echo "+++ Removing old junit reports from: ${juDIR} " |
| rm -f "${juDIR}"/junit-* |
| } |
| |
| # Execute a command and record its results |
| function juLog() { |
| suite=""; |
| errfile=/tmp/evErr.$$.log |
| date=`which gdate 2>/dev/null || which date` |
| asserts=00; errors=0; total=0; content="" |
| |
| # parse arguments |
| ya=""; icase="" |
| while [[ -z "$ya" ]]; do |
| case "$1" in |
| -name=*) name=`echo "$1" | ${SED} -e 's/-name=//'`; shift;; |
| -class=*) class=`echo "$1" | ${SED} -e 's/-class=//'`; shift;; |
| -ierror=*) ereg=`echo "$1" | ${SED} -e 's/-ierror=//'`; icase="-i"; shift;; |
| -error=*) ereg=`echo "$1" | ${SED} -e 's/-error=//'`; shift;; |
| -output=*) juDIR=`echo "$1" | ${SED} -e 's/-output=//'`; shift;; |
| *) ya=1;; |
| esac |
| done |
| |
| # create output directory |
| mkdir -p "${juDIR}" || exit |
| # use first arg as name if it was not given |
| if [[ -z "${name}" ]]; then |
| name="${asserts}-$1" |
| shift |
| fi |
| |
| if [[ "${class}" = "" ]]; then |
| class="default" |
| fi |
| |
| suite=${class} |
| |
| # calculate command to eval |
| [[ -z "$1" ]] && return |
| cmd="$1"; shift |
| while [[ -n "${1:-}" ]] |
| do |
| cmd="${cmd} \"$1\"" |
| shift |
| done |
| |
| # eval the command sending output to a file |
| outf=/var/tmp/ju$$.txt |
| errf=/var/tmp/ju$$-err.txt |
| >${outf} |
| echo "" | tee -a ${outf} |
| echo "+++ Running case: ${class}.${name} " | tee -a ${outf} |
| echo "+++ working dir: "`pwd` | tee -a ${outf} |
| echo "+++ command: ${cmd}" | tee -a ${outf} |
| ini=`${date} +%s.%N` |
| # execute the command, temporarily swapping stderr and stdout so they can be tee'd to separate files, |
| # then swapping them back again so that the streams are written correctly for the invoking process |
| ( (eVal "${cmd}" | tee -a ${outf}) 3>&1 1>&2 2>&3 | tee ${errf}) 3>&1 1>&2 2>&3 |
| evErr=`cat ${errfile}` |
| rm -f ${errfile} |
| end=`${date} +%s.%N` |
| echo "+++ exit code: ${evErr}" | tee -a ${outf} |
| |
| # set the appropriate error, based in the exit code and the regex |
| [[ ${evErr} != 0 ]] && err=1 || err=0 |
| out=`cat $outf | ${SED} -e 's/^\([^+]\)/| \1/g'` |
| if [ ${err} = 0 -a -n "${ereg:-}" ]; then |
| H=`echo "${out}" | egrep ${icase} "${ereg}"` |
| [[ -n "${H}" ]] && err=1 |
| fi |
| [[ ${err} != 0 ]] && echo "+++ error: ${err}" | tee -a ${outf} |
| rm -f ${outf} |
| |
| errMsg=`cat ${errf}` |
| rm -f ${errf} |
| # calculate vars |
| asserts=$(($asserts+1)) |
| errors=$(($errors+$err)) |
| time=`echo "${end} ${ini}" | awk '{print $1 - $2}'` |
| total=`echo "${total} ${time}" | awk '{print $1 + $2}'` |
| |
| # write the junit xml report |
| ## failure tag |
| [[ ${err} = 0 ]] && failure="" || failure=" |
| <failure type=\"ScriptError\" message=\"Script Error\"><![CDATA[${errMsg}]]></failure> |
| " |
| ## testcase tag |
| content="${content} |
| <testcase assertions=\"1\" name=\"${name}\" time=\"${time}\" classname=\"${class}\"> |
| ${failure} |
| <system-err><![CDATA[${errMsg}]]></system-err> |
| </testcase> |
| " |
| ## testsuite block |
| |
| if [[ -e "${juDIR}/junit_${suite}.xml" ]]; then |
| # file exists. first update the failures count |
| failCount=`${SED} -n "s/.*testsuite.*failures=\"\([0-9]*\)\".*/\1/p" "${juDIR}/junit_${suite}.xml"` |
| errors=$(($failCount+$errors)) |
| ${SED} -i "0,/failures=\"${failCount}\"/ s/failures=\"${failCount}\"/failures=\"${errors}\"/" "${juDIR}/junit_${suite}.xml" |
| ${SED} -i "0,/errors=\"${failCount}\"/ s/errors=\"${failCount}\"/errors=\"${errors}\"/" "${juDIR}/junit_${suite}.xml" |
| |
| # file exists. Need to append to it. If we remove the testsuite end tag, we can just add it in after. |
| ${SED} -i "s^</testsuite>^^g" ${juDIR}/junit_${suite}.xml ## remove testSuite so we can add it later |
| ${SED} -i "s^</testsuites>^^g" ${juDIR}/junit_${suite}.xml |
| cat <<EOF >> "$juDIR/junit_$suite.xml" |
| ${content:-} |
| </testsuite> |
| </testsuites> |
| EOF |
| |
| else |
| # no file exists. Adding a new file |
| cat <<EOF > "${juDIR}/junit_${suite}.xml" |
| <?xml version="1.0" encoding="UTF-8"?> |
| <testsuites> |
| <testsuite failures="${errors}" assertions="${assertions:-}" name="${suite}" tests="1" errors="${errors}" time="${total}"> |
| ${content:-} |
| </testsuite> |
| </testsuites> |
| EOF |
| fi |
| |
| return ${err} |
| } |