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:
2026-09-13 17:21:09 -04:00
parent a53a5a26a8
commit a4565c80c3
4 changed files with 52 additions and 10 deletions
+7 -4
View File
@@ -9,6 +9,9 @@ no() { echo " FAIL $1 — $2"; FAIL=$((FAIL+1)); }
# assert stdout of a command contains / omits a fixed string
has() { if printf '%s' "$2" | grep -qF -- "$3"; then ok "$1"; else no "$1" "missing: $3"; fi; }
hasnt(){ if printf '%s' "$2" | grep -qF -- "$3"; then no "$1" "forbidden: $3"; else ok "$1"; fi; }
# Octal permission bits. GNU stat spells it -c %a, BSD stat (macOS) -f %OLp;
# neither accepts the other's flag, so try one then the other.
perm() { stat -c '%a' "$1" 2>/dev/null || stat -f '%OLp' "$1"; }
echo "── tokenstore ──"
TMP="$(mktemp -d)"; STORE="$TMP/tokens.json"
@@ -23,9 +26,9 @@ has "list shows client-a" "$LIST" '"client-a"'
has "list shows client-b" "$LIST" '"client-b"'
has "list shows a property" "$LIST" 'sc-domain:a.com'
hasnt "list redacts refresh tokens" "$LIST" 'RT_AAA'
PERM="$(stat -c '%a' "$STORE")"
PERM="$(perm "$STORE")"
[ "$PERM" = "600" ] && ok "store file is 0600" || no "store file 0600" "got $PERM"
DPERM="$(stat -c '%a' "$(dirname "$STORE")")"
DPERM="$(perm "$(dirname "$STORE")")"
[ "$DPERM" = "700" ] && ok "store dir is 0700" || no "store dir 0700" "got $DPERM"
rm -rf "$TMP"
@@ -136,7 +139,7 @@ import safe_fetch as sf
try: sf.safe_fetch("file:///etc/passwd"); print("OK")
except sf.UnsafeTarget: print("REFUSED")')"
has "non-http scheme refused" "$SCHEME" 'REFUSED'
IMP="$(/bin/grep -E "^(import|from) " "$SD/safe_fetch.py" | /bin/grep -cvE "gzip|http\.client|ipaddress|socket|ssl|urllib\.parse")"
IMP="$(grep -E "^(import|from) " "$SD/safe_fetch.py" | grep -cvE "gzip|http\.client|ipaddress|socket|ssl|urllib\.parse" | tr -d ' ')"
[ "$IMP" = "0" ] && ok "safe_fetch is stdlib-only" || no "safe_fetch is stdlib-only" "$IMP non-stdlib imports"
hasnt "no requests dependency" "$(cat "$SD/safe_fetch.py")" 'import requests'
@@ -477,7 +480,7 @@ has "clear reports ok" "$CL" '"status": "ok"'
has "clear reports count" "$CL" '"cleared": 1'
L7="$(python3 "$SD/tokenstore.py" list --file "$S6")"
has "clear empties store" "$L7" '"accounts": []'
PERM6="$(stat -c '%a' "$S6")"
PERM6="$(perm "$S6")"
[ "$PERM6" = "600" ] && ok "store stays 0600 after clear" || no "store 0600 after clear" "got $PERM6"
# via the real fetch.sh dispatch layer
python3 "$SD/tokenstore.py" set --file "$S6" --label back --refresh-token RT_BACK \