fix(portability): stop assuming GNU coreutils flags on macOS
BSD userland rejects or silently ignores several GNU spellings the repo used: - `timeout` is not in a stock macOS at all (Homebrew installs it, and also as `gtimeout`). Without it every gates.sh check exited 127 and was recorded NOT-MET whatever the check actually did — a systematic false negative. The binary is now resolved once, with a pure-bash deadline behind it so the 124 contract still holds where neither binary exists. GATES_TIMEOUT_BIN is overridable with `-` not `:-`, so an empty value forces that fallback: the suite passes 64/64 both ways, timeout cases included. - `touch -d '10 days ago'` is GNU-only; perl's utime is the one spelling both platforms ship. - BSD `wc -l` pads its count with leading spaces, so string compares failed as got[ 48] want[48]. - `sed -i` needs a suffix argument on BSD AND does not expand \n in the replacement, so the release-candidate CHANGELOG edit silently did nothing and the assertion failed for the wrong reason. Rewritten in awk; the RC_TAG=0 mode still REDs on the absent tag, so the test keeps its teeth. - `/bin/grep` does not exist on macOS (grep lives in /usr/bin), and `stat -c` is GNU-only. fast-libs 11/11, seo-data 221/221, release-candidate 5 GREEN / 0 RED.
This commit is contained in:
+33
-1
@@ -30,6 +30,13 @@
|
||||
set -uo pipefail
|
||||
|
||||
TIMEOUT="${GATES_TIMEOUT:-120}"
|
||||
# GNU coreutils' `timeout` ships on Linux but NOT on a stock macOS (Homebrew
|
||||
# installs it as both `timeout` and `gtimeout`). Resolve it once: without it
|
||||
# every check exits 127 and reports NOT-MET whatever the check actually did.
|
||||
# Overridable, and with `-` not `:-` so an explicitly EMPTY value forces the
|
||||
# pure-bash path — that is how the fallback gets exercised on a machine that
|
||||
# does have the binary.
|
||||
GATES_TIMEOUT_BIN="${GATES_TIMEOUT_BIN-$(command -v timeout || command -v gtimeout || true)}"
|
||||
EVIDENCE_CAP=140
|
||||
|
||||
# Module-level parse tables, index-aligned. Bash has no record type; threading
|
||||
@@ -165,9 +172,34 @@ _decisive() { # _decisive <combined-output>
|
||||
|
||||
# Fail-closed: exit 0 AND the marker. A nonzero process never passes because
|
||||
# its error text happens to contain the expected token.
|
||||
# Same contract as `timeout`: run the command, return 124 if it outruns <secs>.
|
||||
# Pure-bash stand-in for a platform shipping neither binary, so the deadline
|
||||
# stays real instead of silently degrading into "every gate NOT-MET".
|
||||
_gates_timeout() { # _gates_timeout <secs> <cmd>...
|
||||
local secs="$1"; shift
|
||||
[ -n "$GATES_TIMEOUT_BIN" ] && { "$GATES_TIMEOUT_BIN" "$secs" "$@"; return $?; }
|
||||
local waited=0 pid
|
||||
"$@" &
|
||||
pid=$!
|
||||
while kill -0 "$pid" 2>/dev/null; do
|
||||
if [ "$waited" -ge "$secs" ]; then
|
||||
# Park the shell's stderr: bash announces a signal-killed job on ITS
|
||||
# stderr, which the caller captures with 2>&1 and would read as output.
|
||||
exec 3>&2 2>/dev/null
|
||||
kill -TERM "$pid" 2>/dev/null || true
|
||||
wait "$pid" 2>/dev/null || true
|
||||
exec 2>&3 3>&-
|
||||
return 124
|
||||
fi
|
||||
sleep 1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
wait "$pid"
|
||||
}
|
||||
|
||||
_run_one() { # _run_one <idx>
|
||||
local i="$1" out rc
|
||||
out="$(timeout "$TIMEOUT" bash -c "${_CHECK[i]}" 2>&1)"
|
||||
out="$(_gates_timeout "$TIMEOUT" bash -c "${_CHECK[i]}" 2>&1)"
|
||||
rc=$?
|
||||
_STATUS[i]="NOT-MET"
|
||||
if [ "$rc" -eq 124 ]; then
|
||||
|
||||
Reference in New Issue
Block a user