...
1#!/usr/bin/env bash
2### Copyright 2010 Manuel Carrasco MoƱino. (manolo at apache.org)
3###
4### Licensed under the Apache License, Version 2.0.
5### You may obtain a copy of it at
6### http://www.apache.org/licenses/LICENSE-2.0
7
8###
9### A library for shell scripts which creates reports in jUnit format.
10### These reports can be used in Jenkins, or any other CI.
11###
12### Usage:
13### - Include this file in your shell script
14### - Use juLog to call your command any time you want to produce a new report
15### Usage: juLog <options> command arguments
16### options:
17### -class="MyClass" : a class name which will be shown in the junit report
18### -name="TestName" : the test name which will be shown in the junit report
19### -error="RegExp" : a regexp which sets the test as failure when the output matches it
20### -ierror="RegExp" : same as -error but case insensitive
21### -output="Path" : path to output directory, defaults to "./results"
22### - Junit reports are left in the folder 'result' under the directory where the script is executed.
23### - Configure Jenkins to parse junit files from the generated folder
24###
25
26asserts=00; errors=0; total=0; content=""
27date="$(which gdate 2>/dev/null || which date)"
28
29# default output folder
30juDIR="$(pwd)/results"
31
32# The name of the suite is calculated based in your script name
33suite=""
34
35if LANG=C sed --help 2>&1 | grep -q GNU; then
36 SED="sed"
37elif which gsed &>/dev/null; then
38 SED="gsed"
39else
40 echo "Failed to find GNU sed as sed or gsed. If you are on Mac: brew install gnu-sed." >&2
41 exit 1
42fi
43
44# A wrapper for the eval method witch allows catching seg-faults and use tee
45errfile=/tmp/evErr.$$.log
46function eVal() {
47 (eval "$1")
48 # stdout and stderr may currently be inverted (see below) so echo may write to stderr
49 echo "$?" 2>&1 | tr -d "\n" > "${errfile}"
50}
51
52# Method to clean old tests
53function juLogClean() {
54 echo "+++ Removing old junit reports from: ${juDIR} "
55 rm -f "${juDIR}"/junit-*
56}
57
58# Execute a command and record its results
59function juLog() {
60 tmpdir=${TEST_TMPDIR:-"/var/tmp"}
61 suite="";
62 errfile=$tmpdir/evErr.$$.log
63 date="$(which gdate 2>/dev/null || which date)"
64 asserts=00; errors=0; total=0; content=""
65
66 # parse arguments
67 ya=""; icase=""
68 while [[ -z "$ya" ]]; do
69 case "$1" in
70 -name=*) name="$(echo "$1" | ${SED} -e 's/-name=//')"; shift;;
71 -class=*) class="$(echo "$1" | ${SED} -e 's/-class=//')"; shift;;
72 -ierror=*) ereg="$(echo "$1" | ${SED} -e 's/-ierror=//')"; icase="-i"; shift;;
73 -error=*) ereg="$(echo "$1" | ${SED} -e 's/-error=//')"; shift;;
74 -output=*) juDIR="$(echo "$1" | ${SED} -e 's/-output=//')"; shift;;
75 *) ya=1;;
76 esac
77 done
78
79 # create output directory
80 mkdir -p "${juDIR}" || exit
81 # use first arg as name if it was not given
82 if [[ -z "${name}" ]]; then
83 name="${asserts}-$1"
84 shift
85 fi
86
87 if [[ "${class}" = "" ]]; then
88 class="default"
89 fi
90
91 suite=${class}
92
93 # calculate command to eval
94 [[ -z "$1" ]] && return
95 cmd="$1"; shift
96 while [[ -n "${1:-}" ]]
97 do
98 cmd="${cmd} \"$1\""
99 shift
100 done
101
102 # eval the command sending output to a file
103 outf=$tmpdir/ju$$.txt
104 errf=$tmpdir/ju$$-err.txt
105 :>${outf}
106 echo "" | tee -a ${outf}
107 echo "+++ Running case: ${class}.${name} " | tee -a ${outf}
108 echo "+++ working dir: $(pwd)" | tee -a ${outf}
109 echo "+++ command: ${cmd}" | tee -a ${outf}
110 ini="$(${date} +%s.%N)"
111 # execute the command, temporarily swapping stderr and stdout so they can be tee'd to separate files,
112 # then swapping them back again so that the streams are written correctly for the invoking process
113 ( (eVal "${cmd}" | tee -a ${outf}) 3>&1 1>&2 2>&3 | tee ${errf}) 3>&1 1>&2 2>&3
114 evErr="$(cat ${errfile})"
115 rm -f ${errfile}
116 end="$(${date} +%s.%N)"
117 echo "+++ exit code: ${evErr}" | tee -a ${outf}
118
119 # set the appropriate error, based in the exit code and the regex
120 [[ ${evErr} != 0 ]] && err=1 || err=0
121 out="$(${SED} -e 's/^\([^+]\)/| \1/g' "$outf")"
122 if [ ${err} = 0 ] && [ -n "${ereg:-}" ]; then
123 H=$(echo "${out}" | grep -E ${icase} "${ereg}")
124 [[ -n "${H}" ]] && err=1
125 fi
126 [[ ${err} != 0 ]] && echo "+++ error: ${err}" | tee -a ${outf}
127 rm -f ${outf}
128
129 errMsg=$(cat ${errf})
130 rm -f ${errf}
131 # calculate vars
132 asserts=$((asserts+1))
133 errors=$((errors+err))
134 time=$(echo "${end} ${ini}" | awk '{print $1 - $2}')
135 total=$(echo "${total} ${time}" | awk '{print $1 + $2}')
136
137 # write the junit xml report
138 ## failure tag
139 [[ ${err} = 0 ]] && failure="" || failure="
140 <failure type=\"ScriptError\" message=\"Script Error\"><![CDATA[${errMsg}]]></failure>
141 "
142 ## testcase tag
143 content="${content}
144 <testcase assertions=\"1\" name=\"${name}\" time=\"${time}\" classname=\"${class}\">
145 ${failure}
146 <system-err><![CDATA[${errMsg}]]></system-err>
147 </testcase>
148 "
149 ## testsuite block
150
151 if [[ -e "${juDIR}/junit_${suite}.xml" ]]; then
152 # file exists. first update the failures count
153 failCount=$(${SED} -n "s/.*testsuite.*failures=\"\([0-9]*\)\".*/\1/p" "${juDIR}/junit_${suite}.xml")
154 errors=$((failCount+errors))
155 ${SED} -i "0,/failures=\"${failCount}\"/ s/failures=\"${failCount}\"/failures=\"${errors}\"/" "${juDIR}/junit_${suite}.xml"
156 ${SED} -i "0,/errors=\"${failCount}\"/ s/errors=\"${failCount}\"/errors=\"${errors}\"/" "${juDIR}/junit_${suite}.xml"
157
158 # file exists. Need to append to it. If we remove the testsuite end tag, we can just add it in after.
159 ${SED} -i "s^</testsuite>^^g" "${juDIR}/junit_${suite}.xml" ## remove testSuite so we can add it later
160 ${SED} -i "s^</testsuites>^^g" "${juDIR}/junit_${suite}.xml"
161 cat <<EOF >> "$juDIR/junit_$suite.xml"
162 ${content:-}
163 </testsuite>
164</testsuites>
165EOF
166
167 else
168 # no file exists. Adding a new file
169 cat <<EOF > "${juDIR}/junit_${suite}.xml"
170<?xml version="1.0" encoding="UTF-8"?>
171<testsuites>
172 <testsuite failures="${errors}" assertions="${assertions:-}" name="${suite}" tests="1" errors="${errors}" time="${total}">
173 ${content:-}
174 </testsuite>
175</testsuites>
176EOF
177 fi
178
179 return ${err}
180}
View as plain text