a73x

8b70b1e0

test: the Darwin oracle answers on a bare macOS through perl, not coreutils

a73x   2026-09-04 10:16

Commit message
test: the Darwin oracle answers on a bare macOS through perl, not coreutils

The arm spelled greadlink, gdate and gtimeout, which are Homebrew's. A
pristine macOS has none of them, and test/mac.sh runs this file INSIDE a
freshly cloned guest — so the arm did not answer on the one box the gate
exists to grade. Task 7's daemon leg had to canonicalise a path with
`cd -P`/`pwd -P` and say in a comment why it could not call real_path; that
comment is the smell this removes, and the leg asks real_path again.

perl is the answer because it ships with macOS and installs nothing.
Installing a package to answer one question would make the guest something
other than the bare Mac being graded.

real_path is Cwd::realpath with GNU's rule around it: the whole path first,
the parent alone second, so a last component that does not exist yet still
resolves — which e2e_14_upgrade hands it and a plain realpath(3) refuses —
while a missing component above the last is still no answer. Checked
against `readlink -f` on Linux for the existing, the missing-leaf and the
missing-parent cases, output and exit status both.

now_ms is Time::HiRes. The system date would have done on macOS 26, which
does answer %N, but not on an older one, and its failure there is a literal
N inside an arithmetic expansion partway through a run. The snippet holds no
backslash — hence perl's -l rather than a printed newline — because that
text is TYPED into a session by the throughput and agent-mute legs and the
pty fixture reads a backslash as an escape rather than a byte.

The timeout fallback is now three-deep: the GNU binary, then gtimeout, then
perl's alarm. The perl arm is GNU timeout's contract and not an
approximation — 124 when the alarm fired, 127 for a command that is not
there, 128+N for one a signal took, otherwise the command's own status,
each verified against the GNU binary. `refuse()` and half the e2e's daemon
legs turn on telling 124 from 1, so an approximation would grade the wrong
thing. waitpid is restarted around the handler because perl defers a signal
to a safe point and the wait returns EINTR when it does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SakwJEwD9dXBoRP5kWbemW

test/mac.sh
Old New
@@ -308,16 +308,14 @@ gssh 60 >/dev/null <<G || fail daemon "daemon $DPID does not hold $SOCK"
308 . ./os_oracle.sh 308 . ./os_oracle.sh
309 pid_holds_unix_sock $DPID $SOCK 309 pid_holds_unix_sock $DPID $SOCK
310 G 310 G
311 # canonicalised WITHOUT the oracle's real_path: that spelling is greadlink, 311 # Both sides canonical, and both through the oracle: pid_exe answers what
312 # and a pristine macOS has no GNU coreutils on it — installing them to 312 # lsof reports, which resolves every symlink on the way, so a home directory
313 # answer one question would make the guest something other than the bare 313 # or a $TMPDIR reached through one would fail a correct exec unless the
314 # Mac this gate is about. `cd -P` resolves a symlinked directory the same 314 # expected path is resolved the same way.
315 # way, and lsof (which is what pid_exe reads) reports the same canonical
316 # form for the last component.
317 DEXE=$(gssh 60 <<'G' 315 DEXE=$(gssh 60 <<'G'
318 . ./os_oracle.sh 316 . ./os_oracle.sh
319 echo "is=$(pid_exe $(pgrep -x mux | head -1))" 317 echo "is=$(pid_exe $(pgrep -x mux | head -1))"
320 echo "want=$(cd -P -- ./ && pwd -P)/mux" 318 echo "want=$(real_path ./mux)"
321 G 319 G
322 ) 320 )
323 _is=$(printf '%s\n' "$DEXE" | sed -n 's/^is=//p') 321 _is=$(printf '%s\n' "$DEXE" | sed -n 's/^is=//p')
test/oracle_selftest.sh
Old New
@@ -262,9 +262,9 @@ time.sleep(300)' \
262 262
263 # And the TEXT of that clock, run in a shell this one did not configure, 263 # And the TEXT of that clock, run in a shell this one did not configure,
264 # which is exactly how the throughput legs and the agent-mute leg use it. 264 # which is exactly how the throughput legs and the agent-mute leg use it.
265 # On Darwin it names gdate, so a Mac with no coreutils fails HERE rather 265 # On Darwin it is a perl one-liner, so a Mac whose perl has lost
266 # than inside a $(( )) partway through a group file, which is where an 266 # Time::HiRes fails HERE rather than inside a $(( )) partway through a
267 # unpinned snippet would have failed. 267 # group file, which is where an unpinned snippet would have failed.
268 _osnip=$(sh -c "$(now_ms_snippet); now_ms" 2>&1 || true) 268 _osnip=$(sh -c "$(now_ms_snippet); now_ms" 2>&1 || true)
269 _osnipsaid="$_osnip" 269 _osnipsaid="$_osnip"
270 case "$_osnip" in ''|*[!0-9]*) _osnip='' ;; esac 270 case "$_osnip" in ''|*[!0-9]*) _osnip='' ;; esac
test/os_oracle.sh
Old New
@@ -171,27 +171,51 @@ Darwin)
171 file_mode() { stat -f %Lp "$1"; } 171 file_mode() { stat -f %Lp "$1"; }
172 file_size() { stat -f %z "$1"; } 172 file_size() { stat -f %z "$1"; }
173 sha256_of() { shasum -a 256 "$1" | cut -d' ' -f1; } 173 sha256_of() { shasum -a 256 "$1" | cut -d' ' -f1; }
174 # GNU coreutils under their g-names, which is a dependency this harness 174 # perl, and not GNU coreutils under their g-names. A pristine macOS has
175 # already has on Darwin: a bare Mac has no `timeout` either, and the 175 # no Homebrew on it and this arm has to answer there — test/mac.sh runs
176 # gtimeout fallback at the bottom of this file is how that is met. 176 # the oracle INSIDE a freshly cloned guest, and installing a package to
177 # Taking them here rather than reimplementing BSD spellings keeps the 177 # answer one question would make that guest something other than the
178 # two arms asking one question — greadlink -f resolves a path whose last 178 # bare Mac the gate is about. perl ships with macOS and needs nothing.
179 # component does not exist yet, which e2e_14_upgrade hands it and a
180 # realpath(3) would refuse.
181 real_path() { greadlink -f "$1"; }
182 # gdate and not the system date, even though macOS 26's /bin/date does
183 # answer %N (measured, and older ones do not). A `date +%s%N` here would
184 # make the suite's clock depend on how new the OS is, and its failure on
185 # an older one is a literal N inside an arithmetic expansion partway
186 # through a run. gdate is the same GNU date the other arm calls and is
187 # already on any box this harness runs on, for gtimeout's sake.
188 # 179 #
189 # An interpreter instead of either was measured and rejected: python3 180 # Cwd::realpath refuses a path whose last component does not exist,
190 # costs 21.6ms a call and perl 4.5 against gdate's 2.1 (2026-09-03), and 181 # where `readlink -f` resolves it — and e2e_14_upgrade canonicalizes the
191 # the tightest thing a now_ms delta is graded against is the throughput 182 # name of a candidate binary it has not written yet. So the whole path
192 # gate's 10ms ceiling around a 6ms leg — so a python3 start would be 183 # is tried first and the parent alone second, which is GNU's rule; a
193 # most of the reading rather than noise around it. 184 # missing component ABOVE the last is still no answer, and both arms
194 now_ms_snippet() { echo 'now_ms() { echo $(( $(gdate +%s%N) / 1000000 )); }'; } 185 # then return 1 and print nothing.
186 real_path() {
187 perl -e '
188 use Cwd ();
189 my $p = $ARGV[0];
190 my $r = Cwd::realpath($p);
191 unless (defined $r) {
192 my ($d, $b) = $p =~ m{^(.*)/([^/]*)$} ? ($1 eq "" ? "/" : $1, $2) : (".", $p);
193 my $rd = Cwd::realpath($d);
194 exit 1 unless defined $rd;
195 $r = $rd eq "/" ? "/$b" : "$rd/$b";
196 }
197 print "$r\n";' "$1"
198 }
199 # macOS 26's /bin/date does answer %N and older ones do not, so spelling
200 # the system date here would make the suite's clock depend on how new
201 # the OS is — and its failure on an older one is a literal N inside an
202 # arithmetic expansion partway through a run. Time::HiRes is core perl
203 # and answers on every version.
204 #
205 # It costs 4.5ms a call against gdate's 2.1 and python3's 21.6 (measured
206 # 2026-09-03). That is inside the noise of every bracket in the e2e,
207 # whose tightest budget is 1500ms. It is NOT inside the noise of the
208 # throughput gate's 10ms ceiling around a 6ms leg, so a `make throughput`
209 # run on a Mac will need SOLO_MAX_MS raised — which that gate already
210 # takes from the environment for exactly this kind of reason.
211 #
212 # No backslash anywhere in the snippet, hence -l rather than a printed
213 # "\n": this text is TYPED into a session by the throughput and
214 # agent-mute legs, and the pty fixture reads a backslash as the start of
215 # an escape rather than as a byte to send.
216 now_ms_snippet() {
217 echo 'now_ms() { perl -MTime::HiRes -le "print int(Time::HiRes::time() * 1000)"; }'
218 }
195 ;; 219 ;;
196 *) 220 *)
197 pid_alive() { kill -0 "$1" 2>/dev/null || [ -d "/proc/$1" ]; } 221 pid_alive() { kill -0 "$1" 2>/dev/null || [ -d "/proc/$1" ]; }
@@ -255,6 +279,43 @@ esac
255 # cannot drift into two answers on one box. 279 # cannot drift into two answers on one box.
256 eval "$(now_ms_snippet)" 280 eval "$(now_ms_snippet)"
257 281
258 # GNU timeout is a binary here; a box without one names it gtimeout, and a 282 # GNU timeout is a binary on Linux. A Mac has it only if somebody installed
259 # group file keeps spelling `timeout` either way. 283 # coreutils, where it answers to gtimeout, and a pristine one has neither —
260 command -v timeout >/dev/null 2>&1 || timeout() { gtimeout "$@"; } 284 # so the last resort is perl, which every macOS ships. A group file keeps
285 # spelling `timeout` through all three.
286 #
287 # The perl arm is GNU timeout's contract and not an approximation of it:
288 # 124 when the alarm fired, 127 for a command that is not there, 128+N for
289 # one a signal took, and otherwise the command's own status. A harness that
290 # read a timeout as a pass, or a real failure as a timeout, would grade the
291 # wrong thing — `refuse()` and half the e2e's daemon legs turn on telling
292 # 124 from 1.
293 if command -v timeout >/dev/null 2>&1; then
294 :
295 elif command -v gtimeout >/dev/null 2>&1; then
296 timeout() { gtimeout "$@"; }
297 else
298 timeout() {
299 _tsecs=$1; shift
300 # waitpid is restarted around the handler rather than trusted once:
301 # perl defers a signal to a safe point and the wait returns EINTR
302 # when it does, and a status read from that return is not the
303 # child's.
304 perl -e '
305 use POSIX ();
306 use Time::HiRes ();
307 my $secs = shift @ARGV;
308 my $pid = fork();
309 defined $pid or exit 125;
310 unless ($pid) { exec { $ARGV[0] } @ARGV; exit 127; }
311 my $fired = 0;
312 $SIG{ALRM} = sub { $fired = 1; kill "TERM", $pid };
313 Time::HiRes::alarm($secs);
314 my $r;
315 do { $r = waitpid($pid, 0) } while ($r == -1 && $! == POSIX::EINTR());
316 my $st = $?;
317 Time::HiRes::alarm(0);
318 exit 124 if $fired;
319 exit(($st & 127) ? 128 + ($st & 127) : $st >> 8);' "$_tsecs" "$@"
320 }
321 fi