a73x

d81f7277

test: the browser stand-in re-attaches on every `up`, as the page does

a73x   2026-08-26 14:26

Commit message
test: the browser stand-in re-attaches on every `up`, as the page does

mux.js's ENV_CONTROL handler sends an attach whenever the hub says `up`.
No verb modelled that: every scenario scripted a fixed number of
attaches, so a hub that redialled in a tight loop met a fixture that
answered a fixed number of times. A scenario asserting on the hub's DIAL
RATE would have been measuring its own script.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

test/wsclient.zig
Old New
@@ -17,6 +17,8 @@
17 //! expectrefused MS wait for an exit_status that arrived before any 17 //! expectrefused MS wait for an exit_status that arrived before any
18 //! grid — the browser's own refusal discriminator 18 //! grid — the browser's own refusal discriminator
19 //! expectups N MS wait until N `up` control messages have arrived 19 //! expectups N MS wait until N `up` control messages have arrived
20 //! reattach C R MS [NAME] mux.js's ENV_CONTROL handler for MS ms: attach
21 //! once now and once per `up` that follows
20 //! settle QUIET MS drain frames until QUIET ms of silence 22 //! settle QUIET MS drain frames until QUIET ms of silence
21 //! dumpexit write the replica grid (muxd dump format) to 23 //! dumpexit write the replica grid (muxd dump format) to
22 //! --out and exit 0 24 //! --out and exit 0
@@ -523,6 +525,33 @@ pub fn main() !void {
523 if (nowMs() >= deadline) fatal(EXIT_TIMEOUT, "expectups {d} timed out at {d}", .{ want, cl.ups }); 525 if (nowMs() >= deadline) fatal(EXIT_TIMEOUT, "expectups {d} timed out at {d}", .{ want, cl.ups });
524 if (!cl.pump(50)) fatal(EXIT_DIED, "hub hung up during expectups", .{}); 526 if (!cl.pump(50)) fatal(EXIT_DIED, "hub hung up during expectups", .{});
525 } 527 }
528 } else if (std.mem.eql(u8, verb, "reattach")) {
529 // The page's own reflex, which no other verb models: mux.js
530 // re-attaches on EVERY `up`, so a hub that redials in a tight
531 // loop gets a tight loop of attaches back. A scenario that
532 // scripted a fixed number of attaches would measure its own
533 // script instead of the hub's dial rate.
534 var it = std.mem.tokenizeScalar(u8, rest, ' ');
535 const cols = std.fmt.parseInt(u16, it.next() orelse fatal(EXIT_USAGE, "reattach C R MS [NAME]", .{}), 10) catch fatal(EXIT_USAGE, "reattach C R MS [NAME]", .{});
536 const rows = std.fmt.parseInt(u16, it.next() orelse fatal(EXIT_USAGE, "reattach C R MS [NAME]", .{}), 10) catch fatal(EXIT_USAGE, "reattach C R MS [NAME]", .{});
537 const ms = std.fmt.parseInt(i64, it.next() orelse fatal(EXIT_USAGE, "reattach C R MS [NAME]", .{}), 10) catch fatal(EXIT_USAGE, "reattach C R MS [NAME]", .{});
538 const name = it.next() orelse "";
539 if (name.len > 0 and !proto.validSessionName(name))
540 fatal(EXIT_USAGE, "reattach C R MS [NAME]: bad session name {s}", .{name});
541 if (it.next() != null) fatal(EXIT_USAGE, "reattach C R MS [NAME] takes at most four arguments", .{});
542 const deadline = nowMs() + ms;
543 var seen = cl.ups;
544 cl.sendAttach(cols, rows, true, name);
545 while (nowMs() < deadline) {
546 // A hub that hangs up ends the window rather than the run:
547 // the dial count outside is the assertion, and it is
548 // readable either way.
549 if (!cl.pump(50)) break;
550 if (cl.ups != seen) {
551 seen = cl.ups;
552 cl.sendAttach(cols, rows, true, name);
553 }
554 }
526 } else if (std.mem.eql(u8, verb, "settle")) { 555 } else if (std.mem.eql(u8, verb, "settle")) {
527 var it = std.mem.tokenizeScalar(u8, rest, ' '); 556 var it = std.mem.tokenizeScalar(u8, rest, ' ');
528 const quiet = std.fmt.parseInt(i64, it.next() orelse fatal(EXIT_USAGE, "settle QUIET MS", .{}), 10) catch fatal(EXIT_USAGE, "settle QUIET MS", .{}); 557 const quiet = std.fmt.parseInt(i64, it.next() orelse fatal(EXIT_USAGE, "settle QUIET MS", .{}), 10) catch fatal(EXIT_USAGE, "settle QUIET MS", .{});