...
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### -fail="RegExp" : Any line from stderr which contains this pattern becomes part of
22### the failure messsage, without the text matching that pattern.
23### Example: -failure="^ERROR: "
24### Default is to use the entire stderr as failure message.
25### -output="Path" : path to output directory, defaults to "./results"
26### - Junit reports are left in the folder 'result' under the directory where the script is executed.
27### - Configure Jenkins to parse junit files from the generated folder
28###
29
30asserts=00; errors=0; total=0; content=""
31date="$(which gdate 2>/dev/null || which date)"
32
33# default output folder
34juDIR="$(pwd)/results"
35
36# The name of the suite is calculated based in your script name
37suite=""
38
39if LANG=C sed --help 2>&1 | grep -q GNU; then
40 SED="sed"
41elif which gsed &>/dev/null; then
42 SED="gsed"
43else
44 echo "Failed to find GNU sed as sed or gsed. If you are on Mac: brew install gnu-sed." >&2
45 exit 1
46fi
47
48# A wrapper for the eval method witch allows catching seg-faults and use tee
49errfile=/tmp/evErr.$$.log
50function eVal() {
51 (eval "$1")
52 # stdout and stderr may currently be inverted (see below) so echo may write to stderr
53 echo "$?" 2>&1 | tr -d "\n" > "${errfile}"
54}
55
56# Method to clean old tests
57function juLogClean() {
58 echo "+++ Removing old junit reports from: ${juDIR} "
59 rm -f "${juDIR}"/junit-*
60}
61
62# Execute a command and record its results
63function juLog() {
64 suite="";
65 errfile=/tmp/evErr.$$.log
66 date="$(which gdate 2>/dev/null || which date)"
67 asserts=00; errors=0; total=0; content=""
68 local failureRe=""
69
70 # parse arguments
71 ya=""; icase=""
72 while [[ -z "$ya" ]]; do
73 case "$1" in
74 -name=*) name="$(echo "$1" | ${SED} -e 's/-name=//')"; shift;;
75 -class=*) class="$(echo "$1" | ${SED} -e 's/-class=//')"; shift;;
76 -ierror=*) ereg="$(echo "$1" | ${SED} -e 's/-ierror=//')"; icase="-i"; shift;;
77 -error=*) ereg="$(echo "$1" | ${SED} -e 's/-error=//')"; shift;;
78 -fail=*) failureRe="$(echo "$1" | ${SED} -e 's/-fail=//')"; shift;;
79 -output=*) juDIR="$(echo "$1" | ${SED} -e 's/-output=//')"; shift;;
80 *) ya=1;;
81 esac
82 done
83
84 # create output directory
85 mkdir -p "${juDIR}" || exit
86 # use first arg as name if it was not given
87 if [[ -z "${name}" ]]; then
88 name="${asserts}-$1"
89 shift
90 fi
91
92 if [[ "${class}" = "" ]]; then
93 class="default"
94 fi
95
96 suite=${class}
97
98 # calculate command to eval
99 [[ -z "$1" ]] && return
100 cmd="$1"; shift
101 while [[ -n "${1:-}" ]]
102 do
103 cmd="${cmd} \"$1\""
104 shift
105 done
106
107 # eval the command sending output to a file
108 outf=/var/tmp/ju$$.txt
109 errf=/var/tmp/ju$$-err.txt
110 :>${outf}
111 echo "" | tee -a ${outf}
112 echo "+++ Running case: ${class}.${name} " | tee -a ${outf}
113 echo "+++ working dir: $(pwd)" | tee -a ${outf}
114 echo "+++ command: ${cmd}" | tee -a ${outf}
115 ini="$(${date} +%s.%N)"
116 # execute the command, temporarily swapping stderr and stdout so they can be tee'd to separate files,
117 # then swapping them back again so that the streams are written correctly for the invoking process
118 ( (eVal "${cmd}" | tee -a ${outf}) 3>&1 1>&2 2>&3 | tee ${errf}) 3>&1 1>&2 2>&3
119 evErr="$(cat ${errfile})"
120 rm -f ${errfile}
121 end="$(${date} +%s.%N)"
122 echo "+++ exit code: ${evErr}" | tee -a ${outf}
123
124 # set the appropriate error, based in the exit code and the regex
125 [[ ${evErr} -ne 0 ]] && err=1 || err=0
126 out="$(${SED} -e 's/^\([^+]\)/| \1/g' "$outf")"
127 if [ "${err}" -eq 0 ] && [ -n "${ereg:-}" ]; then
128 H=$(echo "${out}" | grep -E ${icase} "${ereg}")
129 [[ -n "${H}" ]] && err=1
130 fi
131 [[ ${err} -ne 0 ]] && echo "+++ error: ${err}" | tee -a ${outf}
132 rm -f ${outf}
133
134 errMsg=$(cat ${errf})
135 rm -f ${errf}
136 # calculate vars
137 asserts=$((asserts+1))
138 errors=$((errors+err))
139 time=$(echo "${end} ${ini}" | awk '{print $1 - $2}')
140 total=$(echo "${total} ${time}" | awk '{print $1 + $2}')
141
142 # write the junit xml report
143 ## failure tag
144 local failure=""
145 if [[ ${err} -ne 0 ]]; then
146 local failureMsg
147 if [ -n "${failureRe}" ]; then
148 failureMsg="$(echo "${errMsg}" | grep -e "${failureRe}" | ${SED} -e "s;${failureRe};;")"
149 if [ -z "${failureMsg}" ]; then
150 failureMsg="see stderr for details"
151 fi
152 else
153 failureMsg="${errMsg}"
154 fi
155 failure="
156 <failure type=\"ScriptError\"><![CDATA[
157${failureMsg}
158]]></failure>
159 "
160 fi
161 ## testcase tag
162 content="${content}
163 <testcase assertions=\"1\" name=\"${name}\" time=\"${time}\" classname=\"${class}\">
164 ${failure}
165 <system-err><![CDATA[${errMsg}]]></system-err>
166 </testcase>
167 "
168 ## testsuite block
169
170 if [[ -e "${juDIR}/junit_${suite}.xml" ]]; then
171 # file exists. first update the failures count
172 failCount=$(${SED} -n "s/.*testsuite.*failures=\"\([0-9]*\)\".*/\1/p" "${juDIR}/junit_${suite}.xml")
173 errors=$((failCount+errors))
174 ${SED} -i "0,/failures=\"${failCount}\"/ s/failures=\"${failCount}\"/failures=\"${errors}\"/" "${juDIR}/junit_${suite}.xml"
175 ${SED} -i "0,/errors=\"${failCount}\"/ s/errors=\"${failCount}\"/errors=\"${errors}\"/" "${juDIR}/junit_${suite}.xml"
176
177 # file exists. Need to append to it. If we remove the testsuite end tag, we can just add it in after.
178 ${SED} -i "s^</testsuite>^^g" "${juDIR}/junit_${suite}.xml" ## remove testSuite so we can add it later
179 ${SED} -i "s^</testsuites>^^g" "${juDIR}/junit_${suite}.xml"
180 cat <<EOF >> "$juDIR/junit_$suite.xml"
181 ${content:-}
182 </testsuite>
183</testsuites>
184EOF
185
186 else
187 # no file exists. Adding a new file
188 cat <<EOF > "${juDIR}/junit_${suite}.xml"
189<?xml version="1.0" encoding="UTF-8"?>
190<testsuites>
191 <testsuite failures="${errors}" assertions="${assertions:-}" name="${suite}" tests="1" errors="${errors}" time="${total}">
192 ${content:-}
193 </testsuite>
194</testsuites>
195EOF
196 fi
197
198 return "${err}"
199}
View as plain text