`copr-cli watch-build` holds a long-lived connection to COPR and can stop responding while the build carries on and finishes normally. The script treated its exit status as the build's outcome, so a watcher that lost its connection was reported as a failed build. Seen in monsoon run 48: COPR build 10835047 succeeded at 08:30:30, the watcher went silent after 08:09:11, and the runner killed the step at 08:35:50. The job was marked failed even though the RPM built and published fine, and that false failure also blocked the dependent version-bump job from running at all. Keep watch-build for its progress stream, but move it to the background, bound it with a timeout, and ignore its exit code. Poll `copr-cli status` for the authoritative state and derive the exit code from that. Polling also means the script returns as soon as the build settles instead of waiting on the watcher to notice. succeeded and skipped (already built) pass; failed and canceled fail; and never reaching a terminal state within COPR_BUILD_TIMEOUT is reported as a timeout rather than silently as a build failure. Adds tests/test-copr-build.sh, which drives the script against a stub copr-cli. The regression case — hung watcher, successful build — fails against the previous script by hanging until killed, which is the production symptom.
102 lines
3.1 KiB
Bash
Executable File
102 lines
3.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# Tests for scripts/copr-build.sh, driven by a stub `copr-cli` on PATH.
|
|
#
|
|
# The case that matters most is a watcher that hangs while the build succeeds
|
|
# anyway: that is what made a green COPR release report as a failed CI job.
|
|
#
|
|
# Usage: tests/test-copr-build.sh
|
|
|
|
set -o pipefail
|
|
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
SCRIPT="$HERE/../scripts/copr-build.sh"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
# Build a stub copr-cli whose behaviour is driven by env vars, and put it first
|
|
# on PATH. STUB_STATES is a space-separated list of states returned by
|
|
# successive `status` calls; the last one repeats.
|
|
make_stub() {
|
|
local dir="$1"
|
|
mkdir -p "$dir"
|
|
cat > "$dir/copr-cli" <<'STUB'
|
|
#!/bin/bash
|
|
case "$1" in
|
|
build)
|
|
echo "Uploading package test.src.rpm"
|
|
echo "Created builds: 12345"
|
|
;;
|
|
watch-build)
|
|
if [ "${STUB_WATCH_HANGS:-0}" = "1" ]; then
|
|
# Mimic a watcher that streams a little then stops responding.
|
|
echo " Build 12345: running"
|
|
sleep 3600
|
|
else
|
|
echo " Build 12345: succeeded"
|
|
fi
|
|
;;
|
|
status)
|
|
# Pop the next state; the final one repeats forever.
|
|
read -r -a states <<< "${STUB_STATES:-succeeded}"
|
|
idx_file="${STUB_IDX_FILE:-/tmp/stub_idx}"
|
|
idx=$(cat "$idx_file" 2>/dev/null || echo 0)
|
|
if [ "$idx" -ge "${#states[@]}" ]; then idx=$(( ${#states[@]} - 1 )); fi
|
|
echo "${states[$idx]}"
|
|
echo $(( idx + 1 )) > "$idx_file"
|
|
;;
|
|
download-build)
|
|
exit 0
|
|
;;
|
|
esac
|
|
STUB
|
|
chmod +x "$dir/copr-cli"
|
|
}
|
|
|
|
run_case() {
|
|
local name="$1" expected_rc="$2" expected_text="$3"
|
|
shift 3
|
|
local tmp out rc
|
|
tmp="$(mktemp -d)"
|
|
make_stub "$tmp/bin"
|
|
out="$(env "$@" STUB_IDX_FILE="$tmp/idx" PATH="$tmp/bin:$PATH" \
|
|
COPR_POLL_INTERVAL=1 COPR_BUILD_TIMEOUT="${CASE_TIMEOUT:-20}" \
|
|
bash "$SCRIPT" owner/project test.src.rpm 2>&1)"
|
|
rc=$?
|
|
if [ "$rc" -eq "$expected_rc" ] && grep -q "$expected_text" <<< "$out"; then
|
|
echo "ok - $name"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL - $name (rc=$rc want $expected_rc; wanted text: $expected_text)"
|
|
sed 's/^/ | /' <<< "$out"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
rm -rf "$tmp"
|
|
}
|
|
|
|
# The regression: watcher stops responding, build succeeded anyway.
|
|
run_case "hung watcher + succeeded build exits 0" \
|
|
0 "final state: succeeded" STUB_WATCH_HANGS=1 STUB_STATES="running succeeded"
|
|
|
|
# A genuine failure must still fail, hung watcher or not.
|
|
run_case "hung watcher + failed build exits 1" \
|
|
1 "finished with state 'failed'" STUB_WATCH_HANGS=1 STUB_STATES="running failed"
|
|
|
|
# Normal path.
|
|
run_case "healthy watcher + succeeded build exits 0" \
|
|
0 "final state: succeeded" STUB_WATCH_HANGS=0 STUB_STATES="succeeded"
|
|
|
|
run_case "canceled build exits 1" \
|
|
1 "finished with state 'canceled'" STUB_WATCH_HANGS=0 STUB_STATES="canceled"
|
|
|
|
# Already-built is not an error.
|
|
run_case "skipped build exits 0" \
|
|
0 "was skipped" STUB_WATCH_HANGS=0 STUB_STATES="skipped"
|
|
|
|
# Never settles: bounded, and reported as a timeout rather than a build failure.
|
|
CASE_TIMEOUT=3 run_case "build that never settles times out" \
|
|
1 "gave up after" STUB_WATCH_HANGS=1 STUB_STATES="running"
|
|
|
|
echo
|
|
echo "passed: $PASS failed: $FAIL"
|
|
[ "$FAIL" -eq 0 ]
|