Files
kubernetes/third_party/forked/shell2junit/sh2ju.sh
Patrick Ohly 03a3288ddd tests: include stdout of failed commands in JUnit
All of the shell commands used by `make test-cmd` log success and
failures to stdout, e.g.:
   get.sh -> if_has_string ->
   2c9153576e/hack/lib/test.sh (L340-L360)

juLog captured stdout in addition to forwarding it to the overall test stdout,
but then only used it to check for text that indicates a problem. The result
was that after a test failure, the only thing visible in Spyglass was a fairly
useless "script error" generated by juLog for its own call chain. One had to
know that the entire build log contained more information and where to look for
it.

Now the stdout text is included in the JUnit file and thus visible when looking
at just one failed test. The output itself is still hard to read (contains one
line saying "FAIL!" and one has to know that the text below that line explains
the failure), but that is a different story.
2025-01-20 09:28:19 +01:00

202 lines
6.9 KiB
Bash
Executable File

#!/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
### -fail="RegExp" : Any line from stderr which contains this pattern becomes part of
### the failure messsage, without the text matching that pattern.
### Example: -fail="^ERROR: "
### Default is to use the entire stderr as failure message.
### -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=""
local failureRe=""
# 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;;
-fail=*) failureRe="$(echo "$1" | ${SED} -e 's/-fail=//')"; 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} -ne 0 ]] && err=1 || err=0
out="$(${SED} -e 's/^\([^+]\)/| \1/g' "$outf")"
if [ "${err}" -eq 0 ] && [ -n "${ereg:-}" ]; then
H=$(echo "${out}" | grep -E ${icase} "${ereg}")
[[ -n "${H}" ]] && err=1
fi
[[ ${err} -ne 0 ]] && echo "+++ error: ${err}" | tee -a ${outf}
outMsg=$(cat ${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
local failure=""
if [[ ${err} -ne 0 ]]; then
local failureMsg
if [ -n "${failureRe}" ]; then
failureMsg="$(echo "${errMsg}" | grep -e "${failureRe}" | ${SED} -e "s;${failureRe};;")"
if [ -z "${failureMsg}" ]; then
failureMsg="see stderr for details"
fi
else
failureMsg="${errMsg}"
fi
failure="
<failure type=\"ScriptError\"><![CDATA[
${failureMsg}
]]></failure>
"
fi
## testcase tag
content="${content}
<testcase assertions=\"1\" name=\"${name}\" time=\"${time}\" classname=\"${class}\">
${failure}
<system-err><![CDATA[${errMsg}]]></system-err>
<system-out><![CDATA[${outMsg}]]></system-out>
</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}"
}