diff --git a/Makefile.am b/Makefile.am index ae3803e..396d511 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,7 +8,7 @@ bin_PROGRAMS = pv dist_doc_DATA = README.md docs/INSTALL docs/COPYING docs/NEWS.md docs/ACKNOWLEDGEMENTS.md docs/DEVELOPERS.md dist_man1_MANS = docs/pv.1 -EXTRA_DIST = docs/pv.1.md docs/benchmark.sh docs/test-on-vm-lab.sh docs/build-package.sh docs/release.sh +EXTRA_DIST = docs/pv.1.md docs/benchmark.sh docs/release.cf pv_SOURCES = \ src/main/debug.c \ diff --git a/docs/build-package.sh b/docs/build-package.sh deleted file mode 100755 index 3f2f07a..0000000 --- a/docs/build-package.sh +++ /dev/null @@ -1,149 +0,0 @@ -#!/bin/sh -# -# Usage: sh build-package.sh source-1.2.3.tar.gz -# -# Given a tar.gz, build a package for the current OS. -# -# Supports these packaging types: -# -# .deb (Debian, Ubuntu, etc) - requires dpkg-dev, debhelper -# .rpm (AlmaLinux, Rocky Linux, OpenSUSE etc) - requires rpmbuild -# -# If the MAINTAINER environment variable is set, this is used as a GPG key -# ID for package signing. -# -# The packages built with this script are not "official", in that they are -# not part of any Linux distribution and may not follow the distribution's -# policies. Use at your own risk. -# -# Copyright 2024-2025 Andrew Wood -# License GPLv3+: GNU GPL version 3 or later; see `docs/COPYING'. - -sourceArchive="$1" -test -n "${sourceArchive}" && test -f "${sourceArchive}" \ -|| { printf "%s: %s: %s\n" "${0##*/}" "${sourceArchive}" "source archive not found" 1>&2; exit 1; } - -packageName="pv" -packageSummary="Shell pipeline element to meter data passing through" -packageDescription="\ -pv (Pipe Viewer) can be inserted into any normal pipeline between two -processes to give a visual indication of how quickly data is passing -through, how long it has taken, how near to completion it is, and an -estimate of how long it will be until completion." -packageUrl="https://ivarch.com/p/pv" -sourceUrl="https://ivarch.com/programs/sources/${packageName}-%{version}.tar.gz" -packageEmail="Andrew Wood " - -workDir="$(mktemp -d)" || exit 1 -trap 'rm -rf "${workDir}"' EXIT - -# Infer the version from the most recent NEWS entry. -version="$(tar xzf "${sourceArchive}" --wildcards -O '*/docs/NEWS.md' 2>/dev/null | awk '/^### [0-9]/{print $2}' | sed -n 1p)" - -if command -v rpmbuild >/dev/null 2>&1; then - # Build an RPM. - cat > "${workDir}"/spec </dev/null 2>&1; then - # Build a Debian package. - mkdir "${workDir}/build" "${workDir}/build/debian" "${workDir}/build/debian/source" - - tar xzf "${sourceArchive}" -C "${workDir}/build" --strip-components=1 - - printf "%s\n" "10" > "${workDir}/build/debian/compat" - touch "${workDir}/build/debian/copyright" - printf "%s\n" "3.0 (quilt)" > "${workDir}/build/debian/source/format" - - cat > "${workDir}/build/debian/rules" < "${workDir}/build/debian/control" <= 9) - -Package: ${packageName} -Architecture: any -Depends: \${misc:Depends} -Description: ${packageSummary} -$(printf "%s\n.\n" "${packageDescription}" | sed 's,^, ,') -EOF - - cat > "${workDir}/build/debian/changelog" <&2 - if test -n "$*"; then - tput rev >&2 - printf " -- %s -- " "$*" >&2 - tput sgr0 >&2 - printf "\n\n" - fi -} - -possiblyDie () { - printf "\n" >&2 - tput bold >&2 - printf "%s: %s\n" "release" "$*" >&2 - tput sgr0 >&2 - printf "\n%s\n" "Type 'y' and hit Enter to continue anyway." - read -r line - test "${line}" = "y" || { status ""; exit 1; } -} - -test -e "${labTestScript}" || labTestScript="" - -# The checklist is re-ordered a little so that we defer making changes (like -# "make indent") until as late as possible, in case any checks fail. - -status "Initial checks" - -# Check MAINTAINER is provided. -test -n "${MAINTAINER}" || possiblyDie "environment variable MAINTAINER is empty" - -# * Check that _po/POTFILES.in_ is up to date -if test -d "${srcdir}/po"; then - inFile="$(sort < "${srcdir}/po/POTFILES.in")" - realList="$(find "${srcdir}" -name "*.c" -printf "%P\n" | sort)" - test "${inFile}" = "${realList}" || possiblyDie "po/POTFILES.in is incorrect" -fi - -status "Initial build" -make || possiblyDie "failed 'make'" - -# * Run "_make analyse_" and see whether remaining warnings can be addressed -status "Source analysis" -make analyse || possiblyDie "failed 'make analyse'" - -# * Version bump and documentation checks: - -status "Version checks" - -# * Check that _docs/NEWS.md_ is up to date -sed -n 1p docs/NEWS.md | grep -Fqi unreleased && possiblyDie "NEWS.md says 'unreleased' on line 1" -versionInNews="$(awk 'FNR==1{print $2}' "${srcdir}/docs/NEWS.md")" -printf "%s\n" "${versionInNews}" | grep -Eq '^[0-9]' || possiblyDie "version in NEWS.md (${versionInNews}) is not numeric" - -# * Check the version in both _configure.ac_ and _docs/NEWS.md_ was updated -versionInConfig="$(grep ^AC_INIT "${srcdir}/configure.ac" | cut -d '[' -f 3 | cut -d ']' -f 1)" -test "${versionInConfig}" = "${versionInNews}" || possiblyDie "version in configure.ac (${versionInConfig}) mismatches NEWS.md (${versionInNews})" - -# * Check that the manuals are up to date -for manPage in ${manuals}; do - versionInManual="$(awk 'FNR==1 {print $5}' "${srcdir}/docs/${manPage}" | cut -d - -f 2)" - test "${versionInManual}" = "${versionInNews}" || possiblyDie "version in ${manPage} (${versionInManual}) mismatches NEWS.md (${versionInNews})" -done - -# * Check that the program version is correct -versionInVersionOutput="$(./"${mainProgram}" --version | awk 'FNR==1{print $NF}')" -test "${versionInVersionOutput}" = "${versionInNews}" || possiblyDie "version in '${mainProgram} --version' (${versionInVersionOutput}) mismatches NEWS.md (${versionInNews})" - -# * Check that the year displayed by --version is correct -yearInVersion="$(./"${mainProgram}" --version | awk '/^Copyright/{print $2}')" -yearNow="$(date '+%Y')" -test "${yearInVersion}" = "${yearNow}" || possiblyDie "the year in '--version' (${yearInVersion}) is not this year (${yearNow})" - -# * Make the Markdown version of the manuals and, if using VPATH, copy the result to the source directory -# We also wipe everything in "docs" so we don't accidentally package -# leftover working files. - -status "Markdown manual" - -rm -f docs/* -for manPage in ${manuals}; do - make "docs/${manPage}.md" - cp "docs/${manPage}.md" "${srcdir}/docs/${manPage}.md" -done - -# * Run "_make indent; make indent indentclean check_" -status "Reformat source" -make indent -make indent indentclean -# The check will run later as part of "make distcheck". - -# * Run "_make -C po update-po_" -status "Update po files" -if test -d "${srcdir}/po"; then - make -C po update-po || possiblyDie "update-po failed" -fi - -# * Run "_autoreconf_" in the source directory -status "Run autoreconf" -(cd "${srcdir}" && autoreconf -is) || possiblyDie "autoreconf failed" - -# * Ensure everything has been committed to the repository -status "Commit check" -gitStatus="$(cd "${srcdir}" && git status --porcelain=v1)" || possiblyDie "failed to run 'git status'" -test -z "${gitStatus}" || possiblyDie "not everything is committed - 'git status' is not empty" - -# * Consistency and build checks: -# * Wipe the build directory, and run "_configure_" there -# * Run "_make distcheck_" -# * Run "_make release MAINTAINER=_" -# NB "make release" implies "make distcheck". -# We set SKIP_VALGRIND_TESTS=1 because the full tests will run in the lab -# check. -status "Release archive" -workDir="$(mktemp -d)" || possiblyDie "mktemp failed" -trap 'chmod -R u+w "${workDir}"; rm -rf "${workDir}"' EXIT -( -cd "${workDir}" || exit 1 -sh "${srcdir}/configure" || exit 1 -make -j8 release SKIP_VALGRIND_TESTS=1 || exit 1 -exit 0 -) || possiblyDie "failed on 'make release'" - -sourceArchive="$(find "${workDir}" -mindepth 1 -maxdepth 1 -type f -name "*.tar.gz")" -test -e "${sourceArchive}.asc" || possiblyDie "release was not signed" - -# * Run "_./configure && make check_" on all test systems, using the _tar.gz_ that was just created -# * Run a cross-compilation check -if test -n "${labTestScript}"; then - status "Lab test" - sh "${labTestScript}" "${sourceArchive}" || possiblyDie "lab test failed" -fi - -# * Update the project web site: -# * Copy the release _.tar.gz_, _.txt_, and _.asc_ files to the web site -# * Use "_pandoc --from markdown --to html_" to convert the news and manual to HTML -status "Release dir" -releaseDir="PV-RELEASE-${versionInNews}" -rm -rf "${releaseDir}" -mkdir "${releaseDir}" -cp "${sourceArchive}" "${sourceArchive}.asc" "${sourceArchive}.txt" "${releaseDir}/" || possiblyDie "failed to copy release files" - -status "HTML docs" -for manPage in ${manuals}; do - pandoc --from markdown --to html --shift-heading-level-by=1 < "${srcdir}/docs/${manPage}.md" > "${releaseDir}/${manPage}.html" -done -pandoc --from markdown --to html < "${srcdir}/docs/NEWS.md" > "${releaseDir}/news.html" - -# Build OS packages. -buildTargets=$(parmo targets) -if test -n "${MAINTAINER}"; then - gpg --export-secret-key --armor "${MAINTAINER}" > "${workDir}/signing-key" \ - || possiblyDie 'failed to export signing key' -fi -for buildTarget in ${buildTargets}; do - status "Package: ${buildTarget}" - buildOK=true - rm -rf "${workDir}/package" - mkdir "${workDir}/package" - if test -s "${workDir}/signing-key"; then - parmo --key "${workDir}/signing-key" --source "${sourceArchive}" --destination "${workDir}/package" --target "${buildTarget}" build-package \ - || possiblyDie "${buildTarget}: build failed" - else - parmo --source "${sourceArchive}" --destination "${workDir}/package" --target "${buildTarget}" build-package \ - || possiblyDie "${buildTarget}: build failed" - fi - find "${workDir}/package" -type f \ - | while read -r packageFile; do - destinationFile="${releaseDir}/${packageFile##*/}" - case "${destinationFile}" in - *.deb) destinationFile="${destinationFile%.deb}_${buildTarget}.deb" ;; - *.txt) destinationFile="" ;; - esac - test -n "${destinationFile}" || continue - cp "${packageFile}" "${destinationFile}" - chmod 644 "${destinationFile}" - done -done - -find "${releaseDir}/" -type f -exec chmod 644 '{}' ';' -find "${releaseDir}/" -type d -exec chmod 755 '{}' ';' - -status "Done" - -cat <&3 - if test -n "${buildHost}"; then - ssh "${buildHost}" mkdir "${remoteBuildDir}" || exit 1 - else - mkdir "${localBuildDir}" || exit 1 - fi - printf '%s %s\n' 'ok' 'created build area' >&3 - - if test -n "${checkHost}"; then - printf '%s %s\n' 'notice' 'creating check area' >&3 - ssh "${checkHost}" mkdir "${remoteBuildDir}" || exit 1 - printf '%s %s\n' 'ok' 'created check area' >&3 - fi - - printf '%s %s\n' 'notice' 'copying source archive' >&3 - if test -n "${buildHost}"; then - scp "${sourceArchive}" "${buildHost}:${remoteBuildDir}/" || exit 1 - elif test -n "${checkHost}"; then - scp "${sourceArchive}" "${checkHost}:${remoteBuildDir}/" || exit 1 - fi - printf '%s %s\n' 'ok' 'source archive copied' >&3 - - printf '%s %s\n' 'notice' 'extracting source archive' >&3 - if test -n "${buildHost}"; then - ssh "${buildHost}" tar xzf "${remoteBuildDir}/${sourceArchive##*/}" -C "${remoteBuildDir}" || exit $? - else - tar xzf "${sourceArchive}" -C "${localBuildDir}" || exit $? - ssh "${checkHost}" tar xzf "${remoteBuildDir}/${sourceArchive##*/}" -C "${remoteBuildDir}" || exit $? - fi - printf '%s %s\n' 'ok' 'source archive extracted' >&3 - - stepResult="ok" - if test -n "${buildHost}"; then - printf '%s %s\n' 'notice' 'configuring' >&3 - ssh "${buildHost}" "cd \"${remoteBuildDir}\" && mkdir BUILD && cd BUILD && ../*/configure ${configureArguments}" || exit $? - else - printf '%s %s\n' 'notice' 'configuring locally' >&3 - ( cd "${localBuildDir}" && mkdir BUILD && cd BUILD && ../*/configure ${configureArguments} ) || exit $? - printf '%s %s\n' 'notice' "configuring on ${checkHost}" >&3 - ssh "${checkHost}" "cd \"${remoteBuildDir}\" && mkdir BUILD && cd BUILD && ../*/configure" \ - || { stepResult="warning"; printf '%s %s\n' 'warning' "configuration failed on ${checkHost}" >&3; } - fi - printf '%s %s\n' "${stepResult}" 'configuration completed' >&3 - - printf '%s %s\n' 'notice' 'building' >&3 - if test -n "${buildHost}"; then - ssh "${buildHost}" "cd \"${remoteBuildDir}/BUILD\" && make -j${concurrency}" || exit $? - else - make -j${concurrency} -C "${localBuildDir}/BUILD" || exit $? - fi - printf '%s %s\n' 'ok' 'build completed' >&3 - - if test -n "${buildHost}"; then - printf '%s %s\n' 'notice' 'testing' >&3 - ssh "${buildHost}" "cd \"${remoteBuildDir}/BUILD\" && make -j${concurrency} check" || exit $? - printf '%s %s\n' 'ok' 'testing completed' >&3 - elif test -n "${checkHost}"; then - printf '%s %s\n' 'notice' "transferring build to ${checkHost}" >&3 - tar cvf "${localBuildDir}/build.tar" -C "${localBuildDir}" BUILD || exit $? - scp "${localBuildDir}/build.tar" "${checkHost}:${remoteBuildDir}/build.tar" || exit $? - ssh "${checkHost}" "tar xf \"${remoteBuildDir}/build.tar\" -C \"${remoteBuildDir}\"" || exit $? - printf '%s %s\n' 'ok' "transferred build to ${checkHost}" >&3 - printf '%s %s\n' 'notice' "testing on ${checkHost}" >&3 - ssh "${checkHost}" "cd \"${remoteBuildDir}/BUILD\" && make -j${concurrency} check-TESTS XCTEST=1" || exit $? - printf '%s %s\n' 'ok' 'testing completed' >&3 - fi - - exit 0 -} - - -# Using directory $1 for metrics and output, run the remaining arguments as -# a command using "scw" to capture metrics and record logs. -# -recordCommand () { - targetDir="$1" - shift - mkdir -p "${targetDir}" - scw -c /dev/null \ - -s UserConfigFile=/dev/null \ - -s ItemsDir=/dev/null \ - -s MetricsDir="${targetDir}" \ - -s CheckLockFile="${targetDir}/.widelock" \ - -s OutputMap= \ - -s OutputMap="OES stamped ${targetDir}/output.log" \ - -s OutputMap="OES raw ${targetDir}/raw-output.log" \ - -s Command="$*" \ - run item -} - -if test "$1" = "--testRunner"; then - testRunner - exit 1 -fi - -labHosts="$(grep -v '^#' ~/.config/lab-hosts 2>/dev/null)" - -workDir=$(mktemp -d) || exit 1 -trap 'rm -rf "${workDir}"' EXIT - -sourceArchive="$1" -shift -hostList="$*" -test -n "${hostList}" || hostList="${labHosts}" - -test -n "${sourceArchive}" || { echo "Usage: ${0##*/} TARBALL [HOST...]"; exit 1; } -test -s "${sourceArchive}" || { echo "${sourceArchive}: not found"; exit 1; } - -attrBold="$(tput bold 2>/dev/null)" -attrUnderline="$(tput smul 2>/dev/null)" -attrRed="$(tput setaf 1 2>/dev/null)" -attrGreen="$(tput setaf 2 2>/dev/null)" -attrYellow="$(tput setaf 3 2>/dev/null)" -attrBlue="$(tput setaf 4 2>/dev/null)" -attrMagenta="$(tput setaf 5 2>/dev/null)" -attrCyan="$(tput setaf 6 2>/dev/null)" -attrWhite="$(tput setaf 7 2>/dev/null)" -attrNone="$(tput sgr0 2>/dev/null)" - -dateStamp="$(date +%Y%m%d-%H%M)" -resultsArchive="result-${dateStamp}.tar.gz" - -startEpoch="$(date +%s)" - - -for hostSpec in ${hostList}; do - localTestDir="${workDir}/${hostSpec}" - remoteBuildDir="test-${dateStamp}.$(date +%s).$$" - buildHost="${hostSpec}" - checkHost="" - configureArguments="" - - xcFor="${hostSpec%:*}" - xcRunOn="${hostSpec#*:}" - - if ! test "${xcFor}" = "${xcRunOn}"; then - localTestDir="${workDir}/${xcFor}-${xcRunOn}" - buildHost="" - checkHost="${xcRunOn}" - configureArguments="--host ${xcFor}" - fi - - mkdir "${localTestDir}" - printf "%s\n" "${hostSpec}" > "${localTestDir}/hostSpec" - - localBuildDir="${localTestDir}/XC" - - ( - flock -x 3 - - export sourceArchive buildHost checkHost remoteBuildDir localBuildDir configureArguments - recordCommand "${localTestDir}" "sh \"$0\" --testRunner" 3<&- - - exec >>"${localTestDir}/output.log" 2>&1 - - printf '*** %s\n' 'retrieving build and test artefacts' - mkdir "${localTestDir}/tests" - if test -n "${buildHost}"; then - scp "${buildHost}:${remoteBuildDir}/BUILD/config.log" "${localTestDir}/" - scp "${buildHost}:${remoteBuildDir}/BUILD/test-suite.log" "${localTestDir}/" - scp "${buildHost}:${remoteBuildDir}/BUILD/tests/*" "${localTestDir}/tests/" - elif test -n "${checkHost}"; then - scp "${checkHost}:${remoteBuildDir}/BUILD/config.log" "${localTestDir}/" - scp "${checkHost}:${remoteBuildDir}/BUILD/test-suite.log" "${localTestDir}/" - scp "${checkHost}:${remoteBuildDir}/BUILD/tests/*" "${localTestDir}/tests/" - fi - - printf '*** %s\n' 'removing build area' - rm -rf "${localBuildDir}" - if test -n "${buildHost}"; then - ssh "${buildHost}" rm -rf "${remoteBuildDir}" - elif test -n "${checkHost}"; then - ssh "${checkHost}" rm -rf "${remoteBuildDir}" - fi - - printf '\n' - - flock -u 3 - ) > "${localTestDir}/framework-output.log" 2>&1 3>>"${localTestDir}/active" & -done - -sleep 0.1 - -allTestHostDirs="$(find "${workDir}" -mindepth 1 -maxdepth 1 -name "[0-9A-Za-z_]*" -type d -printf "%f\n" | sort)" - -hostCount=0 -for testHostDir in ${allTestHostDirs}; do hostCount=$((1+hostCount)); done -exitedCount=0 -overallStatus="" - -while test "${exitedCount}" -lt "${hostCount}"; do - width="$(tput cols 2>/dev/null)" - test -n "${width}" || width=80 - - currentEpoch="$(date +%s)" - elapsedSeconds=$((currentEpoch-startEpoch)) - - { - nameWidth=0 - runningCount=0 - failedCount=0 - passedCount=0 - exitedCount=0 - overallStatus="" - for testHostDir in ${allTestHostDirs}; do - hostSpec="${testHostDir}" - { read -r hostSpec < "${workDir}/${testHostDir}/hostSpec"; } 2>/dev/null - test "${#hostSpec}" -gt "${nameWidth}" && nameWidth="${#hostSpec}" - - exec 3>>"${workDir}/${testHostDir}/active" - flock -x -n 3 && exitedCount=$((1+exitedCount)) - exec 3<&- - - test -e "${workDir}/${testHostDir}/ended" || runningCount=$((1+runningCount)) - if test -e "${workDir}/${testHostDir}/failed"; then - overallStatus="FAIL" - failedCount=$((1+failedCount)) - elif test -e "${workDir}/${testHostDir}/succeeded"; then - test -z "${overallStatus}" && overallStatus="PASS" - passedCount=$((1+passedCount)) - fi - done - test -n "${overallStatus}" || overallStatus="----" - - printf "%sT+%04d - %s%s\n\n" "${attrUnderline}" "${elapsedSeconds}" "$(date '+%Y-%m-%d %H:%M:%S')" "${attrNone}" - - for testHostDir in ${allTestHostDirs}; do - hostSpec="${testHostDir}" - { read -r hostSpec < "${workDir}/${testHostDir}/hostSpec"; } 2>/dev/null - - lastStatusWord="-" - lastStatusMessage="-" - { read -r lastStatusWord lastStatusMessage < "${workDir}/${testHostDir}/last-status"; } 2>/dev/null - - lastLine="-" - test -s "${workDir}/${testHostDir}/output.log" && lastLine="$(tail -n 1 "${workDir}/${testHostDir}/output.log" 2>/dev/null | expand)" - - testResult="----" - test -e "${workDir}/${testHostDir}/failed" && testResult="FAIL" - test -e "${workDir}/${testHostDir}/succeeded" && testResult="PASS" - - remainingWidth=$((width-nameWidth-20)) - statusWidth=$((remainingWidth*2/3)) - test "${statusWidth}" -gt 40 && statusWidth=40 - lastLineWidth=$((remainingWidth-statusWidth)) - - printf " %s%${nameWidth}s%s " "${attrBold}${attrYellow}" "${hostSpec}" "${attrNone}" - case "${testResult}" in - "PASS") printf "%s%s%s" "${attrBold}${attrGreen}" "PASS" "${attrNone}" ;; - "FAIL") printf "%s%s%s" "${attrBold}${attrRed}" "FAIL" "${attrNone}" ;; - *) printf "%s" "----" - esac - - case "${lastStatusWord}" in - "(begin)"|"begin") printf " (%s%s%s) " "${attrBold}${attrMagenta}" "begin" "${attrNone}" ;; - "(end)"|"end") printf " (%s%s%s) " "${attrBold}${attrMagenta}" "end" "${attrNone}" ;; - "(notice)"|"notice") printf " (%s%s%s) " "${attrBold}${attrCyan}" "notice" "${attrNone}" ;; - "(ok)"|"ok") printf " (%s%s%s) " "${attrBold}${attrGreen}" "ok" "${attrNone}" ;; - "(warning)"|"warning") printf " (%s%s%s)" "${attrBold}${attrYellow}" "warning" "${attrNone}" ;; - "(error)"|"error") printf " (%s%s%s) " "${attrBold}${attrRed}" "error" "${attrNone}" ;; - *) printf " %7s " ""; lastStatusMessage="${lastStatusWord} ${lastStatusMessage}" ;; - esac - printf " %s%-${statusWidth}.${statusWidth}s%s" "${attrWhite}" "${lastStatusMessage}" "${attrNone}" - printf " %s%-${lastLineWidth}.${lastLineWidth}s%s" "${attrBlue}" "${lastLine}" "${attrNone}" - printf "\n" - done - - printf "\n%s%s%s " "${attrBold}" "Overall status:" "${attrNone}" - case "${overallStatus}" in - "PASS") printf "%s%s%s" "${attrBold}${attrGreen}" "PASS" "${attrNone}" ;; - "FAIL") printf "%s%s%s" "${attrBold}${attrRed}" "FAIL" "${attrNone}" ;; - *) printf "%s" "----" - esac - - printf " %s%s%s " "${attrBold}" "Running,Passed,Failed:" "${attrNone}" - printf "%sR%d%s" "${attrBold}${attrMagenta}" "${runningCount}" "${attrNone}" - printf ",%sP%d%s" "${attrBold}${attrGreen}" "${passedCount}" "${attrNone}" - printf ",%sF%d%s" "${attrBold}${attrRed}" "${failedCount}" "${attrNone}" - printf " = %d" "${hostCount}" - printf "\n\n" - } > "${workDir}/latest-status.txt" - - cat "${workDir}/latest-status.txt" - sleep 1 -done - -endEpoch="$(date +%s)" -elapsedSeconds=$((endEpoch-startEpoch)) - -printf '%s\n' 'Generating results archive' -tar czf "${resultsArchive}" -C "${workDir}" . - -printf '%s: %d%s\n' 'Total time taken' "${elapsedSeconds}" "s" -printf '%s: %s\n' 'Results archive' "${resultsArchive}" - -test "${overallStatus}" = "PASS" || exit 1