b2deef85
test: the grace table arms its stubborn shells through the fixture doors
a73x 2026-09-04 10:16
Commit message
src/server/server_test_session.zig
| Old | New | ||
|---|---|---|---|
| @@ -738,35 +738,22 @@ test "Server: a table of TERM-ignoring shells costs one grace, not one each" { | |||
| 738 | var td = try h.TestDaemon.open(alloc, "stubborn"); | 738 | var td = try h.TestDaemon.open(alloc, "stubborn"); |
| 739 | defer td.deinit(); | 739 | defer td.deinit(); |
| 740 | 740 | ||
| 741 | // A real shell ignoring the two signals teardown relies on — HUP from | 741 | // The stubborn shell is the harness's, and its doors do not return until |
| 742 | // the master's close, then TERM — which is what an interactive shell | 742 | // that session's own traps are armed. The arming is what makes the child |
| 743 | // does with TERM anyway. Only these children pay the grace at all. | 743 | // ignore HUP and TERM, and only a child that ignores them pays the grace |
| 744 | const shim = try std.fmt.allocPrintSentinel(alloc, "{s}/stubborn.sh", .{td.tmp.path()}, 0); | 744 | // at all, so a shell signalled before its `trap` line ran would let every |
| 745 | defer alloc.free(shim); | 745 | // assertion below hold with no stubborn shell in the table. |
| 746 | { | 746 | try td.startStubborn(alloc, .{}); |
| 747 | var f = try std.fs.cwd().createFile(shim, .{ .mode = 0o755 }); | ||
| 748 | defer f.close(); | ||
| 749 | try f.writeAll("#!/bin/sh\ntrap '' HUP TERM\nwhile :; do sleep 1; done\n"); | ||
| 750 | } | ||
| 751 | |||
| 752 | try td.start(.{ .shell = shim }); | ||
| 753 | 747 | ||
| 754 | // Eight beside the default: enough that serial grace (9 x 500ms) and | 748 | // Eight beside the default: enough that serial grace (9 x 500ms) and |
| 755 | // shared grace (500ms) cannot be confused, cheap enough to spawn. | 749 | // shared grace (500ms) cannot be confused, cheap enough to spawn. |
| 756 | for (1..9) |i| { | 750 | for (1..9) |i| { |
| 757 | var nb: [8]u8 = undefined; | 751 | var nb: [8]u8 = undefined; |
| 758 | const nm = try std.fmt.bufPrint(&nb, "s{d}", .{i}); | 752 | const nm = try std.fmt.bufPrint(&nb, "s{d}", .{i}); |
| 759 | const c = try dial.dialAttachNamed(td.sock_path, 80, 24, nm); | 753 | const c = try td.attachStubborn(alloc, nm, 80, 24); |
| 760 | defer c.close(); | 754 | defer c.close(); |
| 761 | const f = (try awaitFrame(alloc, &td.srv, c.handle, .snapshot, 400)) orelse | ||
| 762 | return error.NoSnapshotFillingTable; | ||
| 763 | f.deinit(alloc); | ||
| 764 | } | 755 | } |
| 765 | 756 | ||
| 766 | // Before signalling, not after: a TERM that lands before `trap` runs is | ||
| 767 | // honoured, and the test would pass without a stubborn child in it. | ||
| 768 | std.Thread.sleep(400 * std.time.ns_per_ms); | ||
| 769 | |||
| 770 | var kids: [max_sessions]std.posix.pid_t = undefined; | 757 | var kids: [max_sessions]std.posix.pid_t = undefined; |
| 771 | var n: usize = 0; | 758 | var n: usize = 0; |
| 772 | for (&td.srv.sessions.table) |*slot| { | 759 | for (&td.srv.sessions.table) |*slot| { |