90c7c32f
The daemon's tests connect like a client: the dial sweep
a73x 2026-08-31 15:37
Commit message
build.zig
| Old | New | ||
|---|---|---|---|
| @@ -187,7 +187,7 @@ const mod_table = [_]ModSpec{ | |||
| 187 | // daemon itself when nobody handed it a --key — and for the shim | 187 | // daemon itself when nobody handed it a --key — and for the shim |
| 188 | // directory shell integration writes under the same 0700 policy. | 188 | // directory shell integration writes under the same 0700 policy. |
| 189 | // `pty` stays a row of its own: the ptyclient fixture consumes it. | 189 | // `pty` stays a row of its own: the ptyclient fixture consumes it. |
| 190 | .{ .name = "daemon", .path = "src/server/server.zig", .link_libc = true, .imports = &.{ "term", "pty", "sockpath", "quic", "xdg", "proxy" }, .test_imports = &.{"testtmp"}, .quic_tests = true }, | 190 | .{ .name = "daemon", .path = "src/server/server.zig", .link_libc = true, .imports = &.{ "term", "pty", "sockpath", "quic", "xdg", "proxy" }, .test_imports = &.{ "testtmp", "dial" }, .quic_tests = true }, |
| 191 | // The agent-facing client. It speaks frames and owns no terminal, which | 191 | // The agent-facing client. It speaks frames and owns no terminal, which |
| 192 | // is the whole point — it attaches at 0x0 and never claims the grid. | 192 | // is the whole point — it attaches at 0x0 and never claims the grid. |
| 193 | // The transport modules are the CLI client's, minus everything that | 193 | // The transport modules are the CLI client's, minus everything that |
src/server/server_test_agent.zig
| Old | New | ||
|---|---|---|---|
| @@ -4,6 +4,7 @@ const quic = @import("quic"); | |||
| 4 | const quic_server = @import("quic_server.zig"); | 4 | const quic_server = @import("quic_server.zig"); |
| 5 | const TmpDir = @import("testtmp").TmpDir; | 5 | const TmpDir = @import("testtmp").TmpDir; |
| 6 | const h = @import("server_test_harness.zig"); | 6 | const h = @import("server_test_harness.zig"); |
| 7 | const dial = h.dial; | ||
| 7 | const srv_mod = @import("server.zig"); | 8 | const srv_mod = @import("server.zig"); |
| 8 | const Server = srv_mod.Server; | 9 | const Server = srv_mod.Server; |
| 9 | const max_agent_chans = srv_mod.max_agent_chans; | 10 | const max_agent_chans = srv_mod.max_agent_chans; |
| @@ -106,9 +107,8 @@ test "Server: agent_offer flags the slot, and an unknown type leaves the client | |||
| 106 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 107 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 107 | defer srv.deinit(); | 108 | defer srv.deinit(); |
| 108 | 109 | ||
| 109 | const c = try std.net.connectUnixSocket(sock_path); | 110 | const c = try dial.dialAttachNamed(sock_path, 80, 24, ""); |
| 110 | defer c.close(); | 111 | defer c.close(); |
| 111 | try attachNamed(c.handle, 80, 24, ""); | ||
| 112 | var spun: usize = 0; | 112 | var spun: usize = 0; |
| 113 | while (spun < 200 and srv.clients[0] == null) : (spun += 1) try srv.pumpOnce(5); | 113 | while (spun < 200 and srv.clients[0] == null) : (spun += 1) try srv.pumpOnce(5); |
| 114 | try std.testing.expect(srv.clients[0] != null); | 114 | try std.testing.expect(srv.clients[0] != null); |
| @@ -221,9 +221,8 @@ test "Server: a session with no agent socket does not inherit the daemon's" { | |||
| 221 | ); | 221 | ); |
| 222 | defer alloc.free(cmd); | 222 | defer alloc.free(cmd); |
| 223 | 223 | ||
| 224 | const c = try std.net.connectUnixSocket(sock_path); | 224 | const c = try dial.dialAttachNamed(sock_path, 80, 24, "nosock"); |
| 225 | defer c.close(); | 225 | defer c.close(); |
| 226 | try attachNamed(c.handle, 80, 24, "nosock"); | ||
| 227 | var spun: usize = 0; | 226 | var spun: usize = 0; |
| 228 | while (spun < 200 and srv.clients[0] == null) : (spun += 1) try srv.pumpOnce(5); | 227 | while (spun < 200 and srv.clients[0] == null) : (spun += 1) try srv.pumpOnce(5); |
| 229 | const si = srv.clients[0].?.session orelse return error.ClientNeverSeated; | 228 | const si = srv.clients[0].?.session orelse return error.ClientNeverSeated; |
| @@ -256,7 +255,7 @@ test "Server: a full channel table refuses the newest dial and says so once" { | |||
| 256 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 255 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 257 | defer srv.deinit(); | 256 | defer srv.deinit(); |
| 258 | 257 | ||
| 259 | const c = try std.net.connectUnixSocket(sock_path); | 258 | const c = try dial.dial(sock_path); |
| 260 | defer c.close(); | 259 | defer c.close(); |
| 261 | try attachOffering(&srv, c.handle, 0, ""); | 260 | try attachOffering(&srv, c.handle, 0, ""); |
| 262 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 261 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| @@ -265,7 +264,7 @@ test "Server: a full channel table refuses the newest dial and says so once" { | |||
| 265 | // so a long-lived agent connection holding a slot is the field failure | 264 | // so a long-lived agent connection holding a slot is the field failure |
| 266 | // this counts — eight of them turn forwarding off for every session. | 265 | // this counts — eight of them turn forwarding off for every session. |
| 267 | var dials: [max_agent_chans + 1]std.net.Stream = undefined; | 266 | var dials: [max_agent_chans + 1]std.net.Stream = undefined; |
| 268 | for (&dials) |*d| d.* = try std.net.connectUnixSocket(path); | 267 | for (&dials) |*d| d.* = try dial.dial(path); |
| 269 | defer for (dials) |d| d.close(); | 268 | defer for (dials) |d| d.close(); |
| 270 | 269 | ||
| 271 | var spun: usize = 0; | 270 | var spun: usize = 0; |
| @@ -293,7 +292,7 @@ test "Server: a full channel table refuses the newest dial and says so once" { | |||
| 293 | // and only a freed slot re-arms the line. ssh retries, so a log that | 292 | // and only a freed slot re-arms the line. ssh retries, so a log that |
| 294 | // scrolled would be its own outage. | 293 | // scrolled would be its own outage. |
| 295 | try std.testing.expect(srv.agents.full_said); | 294 | try std.testing.expect(srv.agents.full_said); |
| 296 | const again = try std.net.connectUnixSocket(path); | 295 | const again = try dial.dial(path); |
| 297 | defer again.close(); | 296 | defer again.close(); |
| 298 | spun = 0; | 297 | spun = 0; |
| 299 | while (spun < 400 and srv.agents.refused_full < 2) : (spun += 1) try srv.pumpOnce(5); | 298 | while (spun < 400 and srv.agents.refused_full < 2) : (spun += 1) try srv.pumpOnce(5); |
| @@ -314,9 +313,8 @@ test "Server: an agent connection with nobody offering is refused fast" { | |||
| 314 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 313 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 315 | defer srv.deinit(); | 314 | defer srv.deinit(); |
| 316 | 315 | ||
| 317 | const c = try std.net.connectUnixSocket(sock_path); | 316 | const c = try dial.dialAttachNamed(sock_path, 80, 24, ""); |
| 318 | defer c.close(); | 317 | defer c.close(); |
| 319 | try attachNamed(c.handle, 80, 24, ""); | ||
| 320 | var spun: usize = 0; | 318 | var spun: usize = 0; |
| 321 | while (spun < 200 and srv.clients[0] == null) : (spun += 1) try srv.pumpOnce(5); | 319 | while (spun < 200 and srv.clients[0] == null) : (spun += 1) try srv.pumpOnce(5); |
| 322 | try std.testing.expect(srv.clients[0] != null); | 320 | try std.testing.expect(srv.clients[0] != null); |
| @@ -328,13 +326,13 @@ test "Server: an agent connection with nobody offering is refused fast" { | |||
| 328 | // exists so a dial can be attributed to one shell; an answerer taken | 326 | // exists so a dial can be attributed to one shell; an answerer taken |
| 329 | // from the daemon at large would hand this session's ssh the key of | 327 | // from the daemon at large would hand this session's ssh the key of |
| 330 | // somebody sitting in front of another one. | 328 | // somebody sitting in front of another one. |
| 331 | const other = try std.net.connectUnixSocket(sock_path); | 329 | const other = try dial.dial(sock_path); |
| 332 | defer other.close(); | 330 | defer other.close(); |
| 333 | try attachOffering(&srv, other.handle, 1, "b"); | 331 | try attachOffering(&srv, other.handle, 1, "b"); |
| 334 | try std.testing.expect(srv.clients[1].?.session != srv.clients[0].?.session); | 332 | try std.testing.expect(srv.clients[1].?.session != srv.clients[0].?.session); |
| 335 | 333 | ||
| 336 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 334 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 337 | const agent = try std.net.connectUnixSocket(path); | 335 | const agent = try dial.dial(path); |
| 338 | defer agent.close(); | 336 | defer agent.close(); |
| 339 | 337 | ||
| 340 | // EOF, and quickly: ssh reads a closed agent socket as "agent refused | 338 | // EOF, and quickly: ssh reads a closed agent socket as "agent refused |
| @@ -357,12 +355,12 @@ test "Server: agent bytes pump both ways through a channel" { | |||
| 357 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 355 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 358 | defer srv.deinit(); | 356 | defer srv.deinit(); |
| 359 | 357 | ||
| 360 | const c = try std.net.connectUnixSocket(sock_path); | 358 | const c = try dial.dial(sock_path); |
| 361 | defer c.close(); | 359 | defer c.close(); |
| 362 | try attachOffering(&srv, c.handle, 0, ""); | 360 | try attachOffering(&srv, c.handle, 0, ""); |
| 363 | 361 | ||
| 364 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 362 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 365 | const agent = try std.net.connectUnixSocket(path); | 363 | const agent = try dial.dial(path); |
| 366 | defer agent.close(); | 364 | defer agent.close(); |
| 367 | 365 | ||
| 368 | // The daemon allocates the id and announces the channel; the client | 366 | // The daemon allocates the id and announces the channel; the client |
| @@ -414,10 +412,10 @@ test "Server: an agent connection is routed to the latest-active offerer" { | |||
| 414 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 412 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 415 | defer srv.deinit(); | 413 | defer srv.deinit(); |
| 416 | 414 | ||
| 417 | const ca = try std.net.connectUnixSocket(sock_path); | 415 | const ca = try dial.dial(sock_path); |
| 418 | defer ca.close(); | 416 | defer ca.close(); |
| 419 | try attachOffering(&srv, ca.handle, 0, ""); | 417 | try attachOffering(&srv, ca.handle, 0, ""); |
| 420 | const cb = try std.net.connectUnixSocket(sock_path); | 418 | const cb = try dial.dial(sock_path); |
| 421 | defer cb.close(); | 419 | defer cb.close(); |
| 422 | try attachOffering(&srv, cb.handle, 1, ""); | 420 | try attachOffering(&srv, cb.handle, 1, ""); |
| 423 | 421 | ||
| @@ -435,7 +433,7 @@ test "Server: an agent connection is routed to the latest-active offerer" { | |||
| 435 | } | 433 | } |
| 436 | try std.testing.expect(srv.clients[1].?.activity > srv.clients[0].?.activity); | 434 | try std.testing.expect(srv.clients[1].?.activity > srv.clients[0].?.activity); |
| 437 | 435 | ||
| 438 | const first = try std.net.connectUnixSocket(path); | 436 | const first = try dial.dial(path); |
| 439 | defer first.close(); | 437 | defer first.close(); |
| 440 | const to_b = (try awaitFrame(alloc, &srv, cb.handle, .agent_open, 200)) orelse | 438 | const to_b = (try awaitFrame(alloc, &srv, cb.handle, .agent_open, 200)) orelse |
| 441 | return error.NoAgentOpenForB; | 439 | return error.NoAgentOpenForB; |
| @@ -458,7 +456,7 @@ test "Server: an agent connection is routed to the latest-active offerer" { | |||
| 458 | } | 456 | } |
| 459 | try std.testing.expect(srv.clients[0].?.activity > srv.clients[1].?.activity); | 457 | try std.testing.expect(srv.clients[0].?.activity > srv.clients[1].?.activity); |
| 460 | 458 | ||
| 461 | const second = try std.net.connectUnixSocket(path); | 459 | const second = try dial.dial(path); |
| 462 | defer second.close(); | 460 | defer second.close(); |
| 463 | const to_a = (try awaitFrame(alloc, &srv, ca.handle, .agent_open, 200)) orelse | 461 | const to_a = (try awaitFrame(alloc, &srv, ca.handle, .agent_open, 200)) orelse |
| 464 | return error.NoAgentOpenForA; | 462 | return error.NoAgentOpenForA; |
| @@ -482,16 +480,16 @@ test "Server: agent_data for an unknown or another client's channel is dropped" | |||
| 482 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 480 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 483 | defer srv.deinit(); | 481 | defer srv.deinit(); |
| 484 | 482 | ||
| 485 | const ca = try std.net.connectUnixSocket(sock_path); | 483 | const ca = try dial.dial(sock_path); |
| 486 | defer ca.close(); | 484 | defer ca.close(); |
| 487 | try attachOffering(&srv, ca.handle, 0, ""); | 485 | try attachOffering(&srv, ca.handle, 0, ""); |
| 488 | const cb = try std.net.connectUnixSocket(sock_path); | 486 | const cb = try dial.dial(sock_path); |
| 489 | defer cb.close(); | 487 | defer cb.close(); |
| 490 | try attachOffering(&srv, cb.handle, 1, ""); | 488 | try attachOffering(&srv, cb.handle, 1, ""); |
| 491 | 489 | ||
| 492 | // B attached last, so the channel is B's. | 490 | // B attached last, so the channel is B's. |
| 493 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 491 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 494 | const agent = try std.net.connectUnixSocket(path); | 492 | const agent = try dial.dial(path); |
| 495 | defer agent.close(); | 493 | defer agent.close(); |
| 496 | const open = (try awaitFrame(alloc, &srv, cb.handle, .agent_open, 200)) orelse | 494 | const open = (try awaitFrame(alloc, &srv, cb.handle, .agent_open, 200)) orelse |
| 497 | return error.NoAgentOpen; | 495 | return error.NoAgentOpen; |
| @@ -546,12 +544,12 @@ test "Server: an agent_data frame past the cap hangs the channel up" { | |||
| 546 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 544 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 547 | defer srv.deinit(); | 545 | defer srv.deinit(); |
| 548 | 546 | ||
| 549 | const c = try std.net.connectUnixSocket(sock_path); | 547 | const c = try dial.dial(sock_path); |
| 550 | defer c.close(); | 548 | defer c.close(); |
| 551 | try attachOffering(&srv, c.handle, 0, ""); | 549 | try attachOffering(&srv, c.handle, 0, ""); |
| 552 | 550 | ||
| 553 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 551 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 554 | const agent = try std.net.connectUnixSocket(path); | 552 | const agent = try dial.dial(path); |
| 555 | defer agent.close(); | 553 | defer agent.close(); |
| 556 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse | 554 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse |
| 557 | return error.NoAgentOpen; | 555 | return error.NoAgentOpen; |
| @@ -598,12 +596,12 @@ test "Server: a client closing a channel hangs up the agent connection without a | |||
| 598 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 596 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 599 | defer srv.deinit(); | 597 | defer srv.deinit(); |
| 600 | 598 | ||
| 601 | const c = try std.net.connectUnixSocket(sock_path); | 599 | const c = try dial.dial(sock_path); |
| 602 | defer c.close(); | 600 | defer c.close(); |
| 603 | try attachOffering(&srv, c.handle, 0, ""); | 601 | try attachOffering(&srv, c.handle, 0, ""); |
| 604 | 602 | ||
| 605 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 603 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 606 | const agent = try std.net.connectUnixSocket(path); | 604 | const agent = try dial.dial(path); |
| 607 | defer agent.close(); | 605 | defer agent.close(); |
| 608 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse | 606 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse |
| 609 | return error.NoAgentOpen; | 607 | return error.NoAgentOpen; |
| @@ -640,12 +638,12 @@ test "Server: an agent listener and the connections it accepts are close-on-exec | |||
| 640 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 638 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 641 | defer srv.deinit(); | 639 | defer srv.deinit(); |
| 642 | 640 | ||
| 643 | const c = try std.net.connectUnixSocket(sock_path); | 641 | const c = try dial.dial(sock_path); |
| 644 | defer c.close(); | 642 | defer c.close(); |
| 645 | try attachOffering(&srv, c.handle, 0, ""); | 643 | try attachOffering(&srv, c.handle, 0, ""); |
| 646 | 644 | ||
| 647 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 645 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 648 | const agent = try std.net.connectUnixSocket(path); | 646 | const agent = try dial.dial(path); |
| 649 | defer agent.close(); | 647 | defer agent.close(); |
| 650 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse | 648 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse |
| 651 | return error.NoAgentOpen; | 649 | return error.NoAgentOpen; |
| @@ -675,11 +673,11 @@ test "Server: a client's agent channels die with the client" { | |||
| 675 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 673 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 676 | defer srv.deinit(); | 674 | defer srv.deinit(); |
| 677 | 675 | ||
| 678 | const c = try std.net.connectUnixSocket(sock_path); | 676 | const c = try dial.dial(sock_path); |
| 679 | try attachOffering(&srv, c.handle, 0, ""); | 677 | try attachOffering(&srv, c.handle, 0, ""); |
| 680 | 678 | ||
| 681 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 679 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 682 | const agent = try std.net.connectUnixSocket(path); | 680 | const agent = try dial.dial(path); |
| 683 | defer agent.close(); | 681 | defer agent.close(); |
| 684 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse | 682 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse |
| 685 | return error.NoAgentOpen; | 683 | return error.NoAgentOpen; |
| @@ -732,7 +730,7 @@ test "Server: a QUIC client's agent channels die with the client" { | |||
| 732 | if (srv.clients[0] == null or !srv.clients[0].?.agent_offer) return error.ClientNeverOffered; | 730 | if (srv.clients[0] == null or !srv.clients[0].?.agent_offer) return error.ClientNeverOffered; |
| 733 | 731 | ||
| 734 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 732 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 735 | const agent = try std.net.connectUnixSocket(path); | 733 | const agent = try dial.dial(path); |
| 736 | defer agent.close(); | 734 | defer agent.close(); |
| 737 | try quicPump(&srv, &only, 10000, &cl, struct { | 735 | try quicPump(&srv, &only, 10000, &cl, struct { |
| 738 | fn f(t: *quic_server.TestPeer) bool { | 736 | fn f(t: *quic_server.TestPeer) bool { |
| @@ -790,12 +788,12 @@ test "Server: an offerer that never answers its first request is hung up on and | |||
| 790 | defer srv.deinit(); | 788 | defer srv.deinit(); |
| 791 | srv.agents.answer_ms = 150; | 789 | srv.agents.answer_ms = 150; |
| 792 | 790 | ||
| 793 | const c = try std.net.connectUnixSocket(sock_path); | 791 | const c = try dial.dial(sock_path); |
| 794 | defer c.close(); | 792 | defer c.close(); |
| 795 | try attachOffering(&srv, c.handle, 0, ""); | 793 | try attachOffering(&srv, c.handle, 0, ""); |
| 796 | 794 | ||
| 797 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 795 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 798 | const agent = try std.net.connectUnixSocket(path); | 796 | const agent = try dial.dial(path); |
| 799 | defer agent.close(); | 797 | defer agent.close(); |
| 800 | const id = try openAndAsk(alloc, &srv, c.handle, agent.handle); | 798 | const id = try openAndAsk(alloc, &srv, c.handle, agent.handle); |
| 801 | 799 | ||
| @@ -815,7 +813,7 @@ test "Server: an offerer that never answers its first request is hung up on and | |||
| 815 | // And it is no longer an offerer: the next dial must not route here | 813 | // And it is no longer an offerer: the next dial must not route here |
| 816 | // again, or every ssh pays the bound before falling through. | 814 | // again, or every ssh pays the bound before falling through. |
| 817 | try std.testing.expect(!srv.clients[0].?.agent_offer); | 815 | try std.testing.expect(!srv.clients[0].?.agent_offer); |
| 818 | const again = try std.net.connectUnixSocket(path); | 816 | const again = try dial.dial(path); |
| 819 | defer again.close(); | 817 | defer again.close(); |
| 820 | try std.testing.expectEqual(@as(?usize, 0), try pumpUntilReadable(&srv, again.handle, &buf, 60)); | 818 | try std.testing.expectEqual(@as(?usize, 0), try pumpUntilReadable(&srv, again.handle, &buf, 60)); |
| 821 | try std.testing.expectEqual(@as(u32, 1), srv.agents.refused_no_offer); | 819 | try std.testing.expectEqual(@as(u32, 1), srv.agents.refused_no_offer); |
| @@ -833,12 +831,12 @@ test "Server: a channel that has answered once is never timed out" { | |||
| 833 | defer srv.deinit(); | 831 | defer srv.deinit(); |
| 834 | srv.agents.answer_ms = 150; | 832 | srv.agents.answer_ms = 150; |
| 835 | 833 | ||
| 836 | const c = try std.net.connectUnixSocket(sock_path); | 834 | const c = try dial.dial(sock_path); |
| 837 | defer c.close(); | 835 | defer c.close(); |
| 838 | try attachOffering(&srv, c.handle, 0, ""); | 836 | try attachOffering(&srv, c.handle, 0, ""); |
| 839 | 837 | ||
| 840 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 838 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 841 | const agent = try std.net.connectUnixSocket(path); | 839 | const agent = try dial.dial(path); |
| 842 | defer agent.close(); | 840 | defer agent.close(); |
| 843 | const id = try openAndAsk(alloc, &srv, c.handle, agent.handle); | 841 | const id = try openAndAsk(alloc, &srv, c.handle, agent.handle); |
| 844 | 842 | ||
| @@ -876,12 +874,12 @@ test "Server: bytes a client sends before it was asked prove nothing" { | |||
| 876 | defer srv.deinit(); | 874 | defer srv.deinit(); |
| 877 | srv.agents.answer_ms = 150; | 875 | srv.agents.answer_ms = 150; |
| 878 | 876 | ||
| 879 | const c = try std.net.connectUnixSocket(sock_path); | 877 | const c = try dial.dial(sock_path); |
| 880 | defer c.close(); | 878 | defer c.close(); |
| 881 | try attachOffering(&srv, c.handle, 0, ""); | 879 | try attachOffering(&srv, c.handle, 0, ""); |
| 882 | 880 | ||
| 883 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 881 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 884 | const agent = try std.net.connectUnixSocket(path); | 882 | const agent = try dial.dial(path); |
| 885 | defer agent.close(); | 883 | defer agent.close(); |
| 886 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse | 884 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse |
| 887 | return error.NoAgentOpen; | 885 | return error.NoAgentOpen; |
| @@ -919,12 +917,12 @@ test "Server: a channel nobody has asked anything on is not timed out" { | |||
| 919 | defer srv.deinit(); | 917 | defer srv.deinit(); |
| 920 | srv.agents.answer_ms = 150; | 918 | srv.agents.answer_ms = 150; |
| 921 | 919 | ||
| 922 | const c = try std.net.connectUnixSocket(sock_path); | 920 | const c = try dial.dial(sock_path); |
| 923 | defer c.close(); | 921 | defer c.close(); |
| 924 | try attachOffering(&srv, c.handle, 0, ""); | 922 | try attachOffering(&srv, c.handle, 0, ""); |
| 925 | 923 | ||
| 926 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; | 924 | const path = srv.ses(0).agent_path orelse return error.NoAgentSocket; |
| 927 | const agent = try std.net.connectUnixSocket(path); | 925 | const agent = try dial.dial(path); |
| 928 | defer agent.close(); | 926 | defer agent.close(); |
| 929 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse | 927 | const open = (try awaitFrame(alloc, &srv, c.handle, .agent_open, 200)) orelse |
| 930 | return error.NoAgentOpen; | 928 | return error.NoAgentOpen; |
src/server/server_test_attach.zig
| Old | New | ||
|---|---|---|---|
| @@ -3,12 +3,12 @@ const Engine = @import("term").engine.Engine; | |||
| 3 | const proto = @import("term").protocol; | 3 | const proto = @import("term").protocol; |
| 4 | const TmpDir = @import("testtmp").TmpDir; | 4 | const TmpDir = @import("testtmp").TmpDir; |
| 5 | const h = @import("server_test_harness.zig"); | 5 | const h = @import("server_test_harness.zig"); |
| 6 | const dial = h.dial; | ||
| 6 | const srv_mod = @import("server.zig"); | 7 | const srv_mod = @import("server.zig"); |
| 7 | const Server = srv_mod.Server; | 8 | const Server = srv_mod.Server; |
| 8 | const installSignalHandlers = srv_mod.installSignalHandlers; | 9 | const installSignalHandlers = srv_mod.installSignalHandlers; |
| 9 | const max_clients = srv_mod.max_clients; | 10 | const max_clients = srv_mod.max_clients; |
| 10 | const applyFrame = h.applyFrame; | 11 | const applyFrame = h.applyFrame; |
| 11 | const attachNamed = h.attachNamed; | ||
| 12 | const awaitFrame = h.awaitFrame; | 12 | const awaitFrame = h.awaitFrame; |
| 13 | const connectedPair = h.connectedPair; | 13 | const connectedPair = h.connectedPair; |
| 14 | const firstStateFrame = h.firstStateFrame; | 14 | const firstStateFrame = h.firstStateFrame; |
| @@ -33,8 +33,7 @@ test "Server: survives a client that dies without detaching; next attach works" | |||
| 33 | defer stop.store(true, .release); | 33 | defer stop.store(true, .release); |
| 34 | 34 | ||
| 35 | // Client 1 attaches, provokes output, then vanishes without detach. | 35 | // Client 1 attaches, provokes output, then vanishes without detach. |
| 36 | const a = try std.net.connectUnixSocket(sock_path); | 36 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 37 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 38 | try proto.writeFrame(a.handle, .input, "echo pre-kill\n"); | 37 | try proto.writeFrame(a.handle, .input, "echo pre-kill\n"); |
| 39 | std.Thread.sleep(300 * std.time.ns_per_ms); | 38 | std.Thread.sleep(300 * std.time.ns_per_ms); |
| 40 | a.close(); // abrupt: no detach frame | 39 | a.close(); // abrupt: no detach frame |
| @@ -43,9 +42,8 @@ test "Server: survives a client that dies without detaching; next attach works" | |||
| 43 | std.Thread.sleep(300 * std.time.ns_per_ms); | 42 | std.Thread.sleep(300 * std.time.ns_per_ms); |
| 44 | 43 | ||
| 45 | // Daemon must still be serving: a fresh attach gets a snapshot. | 44 | // Daemon must still be serving: a fresh attach gets a snapshot. |
| 46 | const b = try std.net.connectUnixSocket(sock_path); | 45 | const b = try dial.dialAttach(sock_path, 80, 24); |
| 47 | defer b.close(); | 46 | defer b.close(); |
| 48 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 49 | var got_snapshot = false; | 47 | var got_snapshot = false; |
| 50 | var deadline_ms: u64 = 5000; | 48 | var deadline_ms: u64 = 5000; |
| 51 | while (deadline_ms > 0 and !got_snapshot) { | 49 | while (deadline_ms > 0 and !got_snapshot) { |
| @@ -79,9 +77,8 @@ test "Server: serves scrollback chunks on request" { | |||
| 79 | defer th.join(); | 77 | defer th.join(); |
| 80 | defer stop.store(true, .release); | 78 | defer stop.store(true, .release); |
| 81 | 79 | ||
| 82 | const c = try std.net.connectUnixSocket(sock_path); | 80 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 83 | defer c.close(); | 81 | defer c.close(); |
| 84 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 85 | try proto.writeFrame(c.handle, .input, "seq 1 100\n"); | 82 | try proto.writeFrame(c.handle, .input, "seq 1 100\n"); |
| 86 | 83 | ||
| 87 | // Wait until an update reports enough history, then fetch the oldest | 84 | // Wait until an update reports enough history, then fetch the oldest |
| @@ -154,17 +151,15 @@ test "Server: a full session refuses the next attach instead of displacing anyon | |||
| 154 | var opened: usize = 0; | 151 | var opened: usize = 0; |
| 155 | defer for (streams[0..opened]) |s| s.close(); | 152 | defer for (streams[0..opened]) |s| s.close(); |
| 156 | while (opened < max_clients) : (opened += 1) { | 153 | while (opened < max_clients) : (opened += 1) { |
| 157 | streams[opened] = try std.net.connectUnixSocket(sock_path); | 154 | streams[opened] = try dial.dialAttach(sock_path, 80, 24); |
| 158 | const fd = streams[opened].handle; | 155 | const fd = streams[opened].handle; |
| 159 | try proto.writeFrame(fd, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 160 | const first = try firstStateFrame(alloc, fd, 10_000); | 156 | const first = try firstStateFrame(alloc, fd, 10_000); |
| 161 | try std.testing.expect(first != null); | 157 | try std.testing.expect(first != null); |
| 162 | } | 158 | } |
| 163 | 159 | ||
| 164 | // The ninth attach is refused; nobody already attached is evicted. | 160 | // The ninth attach is refused; nobody already attached is evicted. |
| 165 | const extra = try std.net.connectUnixSocket(sock_path); | 161 | const extra = try dial.dialAttach(sock_path, 80, 24); |
| 166 | defer extra.close(); | 162 | defer extra.close(); |
| 167 | try proto.writeFrame(extra.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 168 | var refused = false; | 163 | var refused = false; |
| 169 | var deadline_ms: u64 = 5000; | 164 | var deadline_ms: u64 = 5000; |
| 170 | while (deadline_ms > 0 and !refused) { | 165 | while (deadline_ms > 0 and !refused) { |
| @@ -224,12 +219,10 @@ test "Server: two clients converge on one session" { | |||
| 224 | defer th.join(); | 219 | defer th.join(); |
| 225 | defer stop.store(true, .release); | 220 | defer stop.store(true, .release); |
| 226 | 221 | ||
| 227 | const a = try std.net.connectUnixSocket(sock_path); | 222 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 228 | defer a.close(); | 223 | defer a.close(); |
| 229 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | 224 | const b = try dial.dialAttach(sock_path, 80, 24); |
| 230 | const b = try std.net.connectUnixSocket(sock_path); | ||
| 231 | defer b.close(); | 225 | defer b.close(); |
| 232 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 233 | 226 | ||
| 234 | // Input through A must reach both replicas. | 227 | // Input through A must reach both replicas. |
| 235 | try proto.writeFrame(a.handle, .input, "echo both-see-this\n"); | 228 | try proto.writeFrame(a.handle, .input, "echo both-see-this\n"); |
| @@ -330,9 +323,8 @@ test "Server: a same-size join snapshots the joiner only" { | |||
| 330 | defer th.join(); | 323 | defer th.join(); |
| 331 | defer stop.store(true, .release); | 324 | defer stop.store(true, .release); |
| 332 | 325 | ||
| 333 | const a = try std.net.connectUnixSocket(sock_path); | 326 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 334 | defer a.close(); | 327 | defer a.close(); |
| 335 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 336 | 328 | ||
| 337 | var replica_a = try Engine.init(alloc, .{ .cols = 80, .rows = 24 }); | 329 | var replica_a = try Engine.init(alloc, .{ .cols = 80, .rows = 24 }); |
| 338 | defer replica_a.deinit(); | 330 | defer replica_a.deinit(); |
| @@ -362,9 +354,8 @@ test "Server: a same-size join snapshots the joiner only" { | |||
| 362 | 354 | ||
| 363 | // B joins at the same size: nothing about A's grid changed, so A must | 355 | // B joins at the same size: nothing about A's grid changed, so A must |
| 364 | // not be repainted. B, which has nothing, must be. | 356 | // not be repainted. B, which has nothing, must be. |
| 365 | const b = try std.net.connectUnixSocket(sock_path); | 357 | const b = try dial.dialAttach(sock_path, 80, 24); |
| 366 | defer b.close(); | 358 | defer b.close(); |
| 367 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 368 | 359 | ||
| 369 | var b_snapshot = false; | 360 | var b_snapshot = false; |
| 370 | deadline_ms = 5000; | 361 | deadline_ms = 5000; |
| @@ -449,9 +440,8 @@ test "Server: latest attacher's size wins; earlier client is resnapshotted at th | |||
| 449 | th.join(); | 440 | th.join(); |
| 450 | }; | 441 | }; |
| 451 | 442 | ||
| 452 | const a = try std.net.connectUnixSocket(sock_path); | 443 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 453 | defer a.close(); | 444 | defer a.close(); |
| 454 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 455 | 445 | ||
| 456 | // A is attached once it has been answered; only then can B's attach be | 446 | // A is attached once it has been answered; only then can B's attach be |
| 457 | // the *later* event this test is about. | 447 | // the *later* event this test is about. |
| @@ -474,9 +464,8 @@ test "Server: latest attacher's size wins; earlier client is resnapshotted at th | |||
| 474 | } | 464 | } |
| 475 | try std.testing.expect(a_attached); | 465 | try std.testing.expect(a_attached); |
| 476 | 466 | ||
| 477 | const b = try std.net.connectUnixSocket(sock_path); | 467 | const b = try dial.dialAttach(sock_path, 100, 30); |
| 478 | defer b.close(); | 468 | defer b.close(); |
| 479 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(100, 30, 0, 0)); | ||
| 480 | 469 | ||
| 481 | // The grid follows the newest attacher, and A is told about it. | 470 | // The grid follows the newest attacher, and A is told about it. |
| 482 | var a_resized = false; | 471 | var a_resized = false; |
| @@ -548,15 +537,13 @@ test "Server: typing claims the grid for the typist (latest-wins on input)" { | |||
| 548 | 537 | ||
| 549 | // A joins first and, being the only client, sets the grid: attach-wins | 538 | // A joins first and, being the only client, sets the grid: attach-wins |
| 550 | // is unchanged by any of this. | 539 | // is unchanged by any of this. |
| 551 | const a = try std.net.connectUnixSocket(sock_path); | 540 | const a = try dial.dialAttach(sock_path, 100, 30); |
| 552 | defer a.close(); | 541 | defer a.close(); |
| 553 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(100, 30, 0, 0)); | ||
| 554 | try std.testing.expect(try awaitSnapshotSize(alloc, a.handle, 100, 30, 10_000)); | 542 | try std.testing.expect(try awaitSnapshotSize(alloc, a.handle, 100, 30, 10_000)); |
| 555 | 543 | ||
| 556 | // B joins at a different size and takes the grid, which A is told about. | 544 | // B joins at a different size and takes the grid, which A is told about. |
| 557 | const b = try std.net.connectUnixSocket(sock_path); | 545 | const b = try dial.dialAttach(sock_path, 80, 24); |
| 558 | defer b.close(); | 546 | defer b.close(); |
| 559 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 560 | try std.testing.expect(try awaitSnapshotSize(alloc, a.handle, 80, 24, 10_000)); | 547 | try std.testing.expect(try awaitSnapshotSize(alloc, a.handle, 80, 24, 10_000)); |
| 561 | try std.testing.expect(try awaitSnapshotSize(alloc, b.handle, 80, 24, 10_000)); | 548 | try std.testing.expect(try awaitSnapshotSize(alloc, b.handle, 80, 24, 10_000)); |
| 562 | 549 | ||
| @@ -646,9 +633,8 @@ test "Server: a size the grid refuses never becomes a claim" { | |||
| 646 | th.join(); | 633 | th.join(); |
| 647 | }; | 634 | }; |
| 648 | 635 | ||
| 649 | const a = try std.net.connectUnixSocket(sock_path); | 636 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 650 | defer a.close(); | 637 | defer a.close(); |
| 651 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 652 | try std.testing.expect(try awaitSnapshotSize(alloc, a.handle, 80, 24, 10_000)); | 638 | try std.testing.expect(try awaitSnapshotSize(alloc, a.handle, 80, 24, 10_000)); |
| 653 | _ = try drainHeld(alloc, a.handle, 10_000); | 639 | _ = try drainHeld(alloc, a.handle, 10_000); |
| 654 | 640 | ||
| @@ -656,9 +642,8 @@ test "Server: a size the grid refuses never becomes a claim" { | |||
| 656 | // It still joins and is still served — it is only its *size* that is | 642 | // It still joins and is still served — it is only its *size* that is |
| 657 | // refused — but the grid must not move, and A must not be repainted for | 643 | // refused — but the grid must not move, and A must not be repainted for |
| 658 | // a resize that never happened. | 644 | // a resize that never happened. |
| 659 | const d = try std.net.connectUnixSocket(sock_path); | 645 | const d = try dial.dialAttach(sock_path, 1, 1); |
| 660 | defer d.close(); | 646 | defer d.close(); |
| 661 | try proto.writeFrame(d.handle, .attach, &proto.encodeAttach(1, 1, 0, 0)); | ||
| 662 | try std.testing.expect(try awaitSnapshotSize(alloc, d.handle, 80, 24, 10_000)); | 647 | try std.testing.expect(try awaitSnapshotSize(alloc, d.handle, 80, 24, 10_000)); |
| 663 | 648 | ||
| 664 | // D types. Its slot holds no accepted size, so this claims nothing: A | 649 | // D types. Its slot holds no accepted size, so this claims nothing: A |
| @@ -725,12 +710,10 @@ test "Server: scrollback fetch is per-client and independent" { | |||
| 725 | defer th.join(); | 710 | defer th.join(); |
| 726 | defer stop.store(true, .release); | 711 | defer stop.store(true, .release); |
| 727 | 712 | ||
| 728 | const a = try std.net.connectUnixSocket(sock_path); | 713 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 729 | defer a.close(); | 714 | defer a.close(); |
| 730 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | 715 | const b = try dial.dialAttach(sock_path, 100, 30); |
| 731 | const b = try std.net.connectUnixSocket(sock_path); | ||
| 732 | defer b.close(); | 716 | defer b.close(); |
| 733 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(100, 30, 0, 0)); | ||
| 734 | 717 | ||
| 735 | // Typed by B; both clients see the history it produces. | 718 | // Typed by B; both clients see the history it produces. |
| 736 | try proto.writeFrame(b.handle, .input, "seq 1 100\n"); | 719 | try proto.writeFrame(b.handle, .input, "seq 1 100\n"); |
| @@ -915,14 +898,13 @@ test "Server: replica rebuilt from snapshots matches the authoritative grid" { | |||
| 915 | defer th.join(); | 898 | defer th.join(); |
| 916 | defer stop.store(true, .release); | 899 | defer stop.store(true, .release); |
| 917 | 900 | ||
| 918 | const stream = try std.net.connectUnixSocket(sock_path); | 901 | const stream = try dial.dialAttach(sock_path, 100, 30); |
| 919 | defer stream.close(); | 902 | defer stream.close(); |
| 920 | const fd = stream.handle; | 903 | const fd = stream.handle; |
| 921 | 904 | ||
| 922 | var replica = try Engine.init(alloc, .{ .cols = 100, .rows = 30 }); | 905 | var replica = try Engine.init(alloc, .{ .cols = 100, .rows = 30 }); |
| 923 | defer replica.deinit(); | 906 | defer replica.deinit(); |
| 924 | 907 | ||
| 925 | try proto.writeFrame(fd, .attach, &proto.encodeAttach(100, 30, 0, 0)); | ||
| 926 | try proto.writeFrame(fd, .input, "printf 'fidelity-%s\\n' ok\n"); | 908 | try proto.writeFrame(fd, .input, "printf 'fidelity-%s\\n' ok\n"); |
| 927 | 909 | ||
| 928 | // Consume the attach snapshot and the deltas that follow it until the | 910 | // Consume the attach snapshot and the deltas that follow it until the |
| @@ -991,9 +973,8 @@ test "Server: typing produces deltas, not snapshots; stats track both" { | |||
| 991 | defer th.join(); | 973 | defer th.join(); |
| 992 | defer stop.store(true, .release); | 974 | defer stop.store(true, .release); |
| 993 | 975 | ||
| 994 | const c = try std.net.connectUnixSocket(sock_path); | 976 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 995 | defer c.close(); | 977 | defer c.close(); |
| 996 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 997 | 978 | ||
| 998 | // The attach is answered with a full snapshot carrying the tracker seq. | 979 | // The attach is answered with a full snapshot carrying the tracker seq. |
| 999 | var attach_seq: u64 = 0; | 980 | var attach_seq: u64 = 0; |
| @@ -1091,9 +1072,8 @@ test "Server: reattach needs a recent have_seq AND this daemon's epoch to get a | |||
| 1091 | var last_seq: u64 = 0; | 1072 | var last_seq: u64 = 0; |
| 1092 | var epoch: u64 = 0; | 1073 | var epoch: u64 = 0; |
| 1093 | { | 1074 | { |
| 1094 | const c1 = try std.net.connectUnixSocket(sock_path); | 1075 | const c1 = try dial.dialAttach(sock_path, 80, 24); |
| 1095 | defer c1.close(); | 1076 | defer c1.close(); |
| 1096 | try proto.writeFrame(c1.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 1097 | // A resize is a discontinuity, so this pins reset_seq above 1 no | 1077 | // A resize is a discontinuity, so this pins reset_seq above 1 no |
| 1098 | // matter how the shell's first output raced the attach — session 3 | 1078 | // matter how the shell's first output raced the attach — session 3 |
| 1099 | // below needs have_seq=1 to be reliably stale. | 1079 | // below needs have_seq=1 to be reliably stale. |
| @@ -1113,7 +1093,7 @@ test "Server: reattach needs a recent have_seq AND this daemon's epoch to get a | |||
| 1113 | // instance. Those seqs describe a different history; honouring one would | 1093 | // instance. Those seqs describe a different history; honouring one would |
| 1114 | // paint this session's screen with another's rows. Snapshot. | 1094 | // paint this session's screen with another's rows. Snapshot. |
| 1115 | { | 1095 | { |
| 1116 | const c2 = try std.net.connectUnixSocket(sock_path); | 1096 | const c2 = try dial.dial(sock_path); |
| 1117 | defer c2.close(); | 1097 | defer c2.close(); |
| 1118 | try proto.writeFrame(c2.handle, .attach, &proto.encodeAttach(80, 24, last_seq, epoch ^ 1)); | 1098 | try proto.writeFrame(c2.handle, .attach, &proto.encodeAttach(80, 24, last_seq, epoch ^ 1)); |
| 1119 | const first = try firstStateFrame(alloc, c2.handle, 10_000); | 1099 | const first = try firstStateFrame(alloc, c2.handle, 10_000); |
| @@ -1130,7 +1110,7 @@ test "Server: reattach needs a recent have_seq AND this daemon's epoch to get a | |||
| 1130 | // Session 3: a real seq with have_epoch = 0 — a pre-epoch client, or one | 1110 | // Session 3: a real seq with have_epoch = 0 — a pre-epoch client, or one |
| 1131 | // hoping 0 means "any". It means "none", and none is never serviceable. | 1111 | // hoping 0 means "any". It means "none", and none is never serviceable. |
| 1132 | { | 1112 | { |
| 1133 | const c3 = try std.net.connectUnixSocket(sock_path); | 1113 | const c3 = try dial.dial(sock_path); |
| 1134 | defer c3.close(); | 1114 | defer c3.close(); |
| 1135 | try proto.writeFrame(c3.handle, .attach, &proto.encodeAttach(80, 24, last_seq, 0)); | 1115 | try proto.writeFrame(c3.handle, .attach, &proto.encodeAttach(80, 24, last_seq, 0)); |
| 1136 | const first = try firstStateFrame(alloc, c3.handle, 10_000); | 1116 | const first = try firstStateFrame(alloc, c3.handle, 10_000); |
| @@ -1143,7 +1123,7 @@ test "Server: reattach needs a recent have_seq AND this daemon's epoch to get a | |||
| 1143 | // Session 4: right seq, right epoch — we really are up to date, so the | 1123 | // Session 4: right seq, right epoch — we really are up to date, so the |
| 1144 | // daemon owes us a delta (possibly empty), never a full repaint. | 1124 | // daemon owes us a delta (possibly empty), never a full repaint. |
| 1145 | { | 1125 | { |
| 1146 | const c4 = try std.net.connectUnixSocket(sock_path); | 1126 | const c4 = try dial.dial(sock_path); |
| 1147 | defer c4.close(); | 1127 | defer c4.close(); |
| 1148 | try proto.writeFrame(c4.handle, .attach, &proto.encodeAttach(80, 24, last_seq, epoch)); | 1128 | try proto.writeFrame(c4.handle, .attach, &proto.encodeAttach(80, 24, last_seq, epoch)); |
| 1149 | const first = try firstStateFrame(alloc, c4.handle, 10_000); | 1129 | const first = try firstStateFrame(alloc, c4.handle, 10_000); |
| @@ -1155,7 +1135,7 @@ test "Server: reattach needs a recent have_seq AND this daemon's epoch to get a | |||
| 1155 | // Session 5: right epoch, but have_seq predates the last discontinuity, | 1135 | // Session 5: right epoch, but have_seq predates the last discontinuity, |
| 1156 | // so no delta can reconstruct our state — full snapshot. | 1136 | // so no delta can reconstruct our state — full snapshot. |
| 1157 | { | 1137 | { |
| 1158 | const c5 = try std.net.connectUnixSocket(sock_path); | 1138 | const c5 = try dial.dial(sock_path); |
| 1159 | defer c5.close(); | 1139 | defer c5.close(); |
| 1160 | try proto.writeFrame(c5.handle, .attach, &proto.encodeAttach(80, 24, 1, epoch)); | 1140 | try proto.writeFrame(c5.handle, .attach, &proto.encodeAttach(80, 24, 1, epoch)); |
| 1161 | const first = try firstStateFrame(alloc, c5.handle, 10_000); | 1141 | const first = try firstStateFrame(alloc, c5.handle, 10_000); |
| @@ -1184,9 +1164,8 @@ test "Server: a daemon restart invalidates have_seq even with the old epoch pres | |||
| 1184 | defer th_a.join(); | 1164 | defer th_a.join(); |
| 1185 | defer stop_a.store(true, .release); | 1165 | defer stop_a.store(true, .release); |
| 1186 | 1166 | ||
| 1187 | const c = try std.net.connectUnixSocket(sock_path); | 1167 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 1188 | defer c.close(); | 1168 | defer c.close(); |
| 1189 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 1190 | try proto.writeFrame(c.handle, .input, "echo before-restart\n"); | 1169 | try proto.writeFrame(c.handle, .input, "echo before-restart\n"); |
| 1191 | held_a = try drainHeld(alloc, c.handle, 10_000); | 1170 | held_a = try drainHeld(alloc, c.handle, 10_000); |
| 1192 | try std.testing.expect(held_a.seq > 0); | 1171 | try std.testing.expect(held_a.seq > 0); |
| @@ -1210,9 +1189,8 @@ test "Server: a daemon restart invalidates have_seq even with the old epoch pres | |||
| 1210 | // constant epoch would satisfy every other test in the file. | 1189 | // constant epoch would satisfy every other test in the file. |
| 1211 | var held_b: Held = undefined; | 1190 | var held_b: Held = undefined; |
| 1212 | { | 1191 | { |
| 1213 | const c = try std.net.connectUnixSocket(sock_path); | 1192 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 1214 | defer c.close(); | 1193 | defer c.close(); |
| 1215 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 1216 | held_b = try drainHeld(alloc, c.handle, 10_000); | 1194 | held_b = try drainHeld(alloc, c.handle, 10_000); |
| 1217 | try std.testing.expect(held_b.seq > 0); | 1195 | try std.testing.expect(held_b.seq > 0); |
| 1218 | try std.testing.expect(held_b.epoch != 0); | 1196 | try std.testing.expect(held_b.epoch != 0); |
| @@ -1224,7 +1202,7 @@ test "Server: a daemon restart invalidates have_seq even with the old epoch pres | |||
| 1224 | // about this attach is serviceable except the epoch, so a delta here | 1202 | // about this attach is serviceable except the epoch, so a delta here |
| 1225 | // would mean the epoch is not being checked at all. | 1203 | // would mean the epoch is not being checked at all. |
| 1226 | { | 1204 | { |
| 1227 | const c = try std.net.connectUnixSocket(sock_path); | 1205 | const c = try dial.dial(sock_path); |
| 1228 | defer c.close(); | 1206 | defer c.close(); |
| 1229 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, held_b.seq, held_a.epoch)); | 1207 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, held_b.seq, held_a.epoch)); |
| 1230 | const first = try firstStateFrame(alloc, c.handle, 10_000); | 1208 | const first = try firstStateFrame(alloc, c.handle, 10_000); |
| @@ -1239,7 +1217,7 @@ test "Server: a daemon restart invalidates have_seq even with the old epoch pres | |||
| 1239 | // held. B has no way to reconstruct that state, so: snapshot, stamped | 1217 | // held. B has no way to reconstruct that state, so: snapshot, stamped |
| 1240 | // with B's own epoch, which the client adopts in place of A's. | 1218 | // with B's own epoch, which the client adopts in place of A's. |
| 1241 | { | 1219 | { |
| 1242 | const c = try std.net.connectUnixSocket(sock_path); | 1220 | const c = try dial.dial(sock_path); |
| 1243 | defer c.close(); | 1221 | defer c.close(); |
| 1244 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, held_a.seq, held_a.epoch)); | 1222 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, held_a.seq, held_a.epoch)); |
| 1245 | const first = try firstStateFrame(alloc, c.handle, 10_000); | 1223 | const first = try firstStateFrame(alloc, c.handle, 10_000); |
| @@ -1412,9 +1390,8 @@ test "Server: a session is created at the attacher's size" { | |||
| 1412 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 1390 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 1413 | defer srv.deinit(); | 1391 | defer srv.deinit(); |
| 1414 | 1392 | ||
| 1415 | const c = try std.net.connectUnixSocket(sock_path); | 1393 | const c = try dial.dialAttachNamed(sock_path, 100, 30, "big"); |
| 1416 | defer c.close(); | 1394 | defer c.close(); |
| 1417 | try attachNamed(c.handle, 100, 30, "big"); | ||
| 1418 | const f = (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400)) orelse | 1395 | const f = (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400)) orelse |
| 1419 | return error.NoSnapshot; | 1396 | return error.NoSnapshot; |
| 1420 | defer f.deinit(alloc); | 1397 | defer f.deinit(alloc); |
| @@ -1443,9 +1420,8 @@ test "Server: a 0x0 attach joins but never creates" { | |||
| 1443 | // mux a send attaches at 0x0 — it makes no size claim. Against a name | 1420 | // mux a send attaches at 0x0 — it makes no size claim. Against a name |
| 1444 | // that does not exist, that must be a refusal, never a shell spawned | 1421 | // that does not exist, that must be a refusal, never a shell spawned |
| 1445 | // at a size nobody has. | 1422 | // at a size nobody has. |
| 1446 | const c1 = try std.net.connectUnixSocket(sock_path); | 1423 | const c1 = try dial.dialAttachNamed(sock_path, 0, 0, "b"); |
| 1447 | defer c1.close(); | 1424 | defer c1.close(); |
| 1448 | try attachNamed(c1.handle, 0, 0, "b"); | ||
| 1449 | const f1 = (try awaitFrame(alloc, &srv, c1.handle, .exit_status, 400)) orelse | 1425 | const f1 = (try awaitFrame(alloc, &srv, c1.handle, .exit_status, 400)) orelse |
| 1450 | return error.NoRefusal; | 1426 | return error.NoRefusal; |
| 1451 | defer f1.deinit(alloc); | 1427 | defer f1.deinit(alloc); |
| @@ -1453,18 +1429,16 @@ test "Server: a 0x0 attach joins but never creates" { | |||
| 1453 | try std.testing.expect(srv.sessions.table[1] == null); | 1429 | try std.testing.expect(srv.sessions.table[1] == null); |
| 1454 | 1430 | ||
| 1455 | // Create it properly, at a real size... | 1431 | // Create it properly, at a real size... |
| 1456 | const c2 = try std.net.connectUnixSocket(sock_path); | 1432 | const c2 = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 1457 | defer c2.close(); | 1433 | defer c2.close(); |
| 1458 | try attachNamed(c2.handle, 80, 24, "b"); | ||
| 1459 | const f2 = (try awaitFrame(alloc, &srv, c2.handle, .snapshot, 400)) orelse | 1434 | const f2 = (try awaitFrame(alloc, &srv, c2.handle, .snapshot, 400)) orelse |
| 1460 | return error.NoSnapshotOnCreate; | 1435 | return error.NoSnapshotOnCreate; |
| 1461 | f2.deinit(alloc); | 1436 | f2.deinit(alloc); |
| 1462 | const si_b = srv.sessions.find("b") orelse return error.NotCreated; | 1437 | const si_b = srv.sessions.find("b") orelse return error.NotCreated; |
| 1463 | 1438 | ||
| 1464 | // ...and the very same 0x0 attach now joins it. | 1439 | // ...and the very same 0x0 attach now joins it. |
| 1465 | const c3 = try std.net.connectUnixSocket(sock_path); | 1440 | const c3 = try dial.dialAttachNamed(sock_path, 0, 0, "b"); |
| 1466 | defer c3.close(); | 1441 | defer c3.close(); |
| 1467 | try attachNamed(c3.handle, 0, 0, "b"); | ||
| 1468 | const f3 = (try awaitFrame(alloc, &srv, c3.handle, .snapshot, 400)) orelse | 1442 | const f3 = (try awaitFrame(alloc, &srv, c3.handle, .snapshot, 400)) orelse |
| 1469 | return error.ZeroSizeJoinRefused; | 1443 | return error.ZeroSizeJoinRefused; |
| 1470 | f3.deinit(alloc); | 1444 | f3.deinit(alloc); |
| @@ -1499,9 +1473,8 @@ test "Server: a 1x1 attach joins but never creates" { | |||
| 1499 | // grid to 1x1, so a session created at 1x1 could never be resized by | 1473 | // grid to 1x1, so a session created at 1x1 could never be resized by |
| 1500 | // the client that caused it — a shell nobody can use, spawned by | 1474 | // the client that caused it — a shell nobody can use, spawned by |
| 1501 | // attaching with a name nobody had created yet. | 1475 | // attaching with a name nobody had created yet. |
| 1502 | const c1 = try std.net.connectUnixSocket(sock_path); | 1476 | const c1 = try dial.dialAttachNamed(sock_path, 1, 1, "tile"); |
| 1503 | defer c1.close(); | 1477 | defer c1.close(); |
| 1504 | try attachNamed(c1.handle, 1, 1, "tile"); | ||
| 1505 | const f1 = (try awaitFrame(alloc, &srv, c1.handle, .exit_status, 400)) orelse | 1478 | const f1 = (try awaitFrame(alloc, &srv, c1.handle, .exit_status, 400)) orelse |
| 1506 | return error.NoRefusal; | 1479 | return error.NoRefusal; |
| 1507 | defer f1.deinit(alloc); | 1480 | defer f1.deinit(alloc); |
| @@ -1509,9 +1482,8 @@ test "Server: a 1x1 attach joins but never creates" { | |||
| 1509 | try std.testing.expect(srv.sessions.find("tile") == null); | 1482 | try std.testing.expect(srv.sessions.find("tile") == null); |
| 1510 | 1483 | ||
| 1511 | // Created at a real size by someone who has one... | 1484 | // Created at a real size by someone who has one... |
| 1512 | const c2 = try std.net.connectUnixSocket(sock_path); | 1485 | const c2 = try dial.dialAttachNamed(sock_path, 80, 24, "tile"); |
| 1513 | defer c2.close(); | 1486 | defer c2.close(); |
| 1514 | try attachNamed(c2.handle, 80, 24, "tile"); | ||
| 1515 | const f2 = (try awaitFrame(alloc, &srv, c2.handle, .snapshot, 400)) orelse | 1487 | const f2 = (try awaitFrame(alloc, &srv, c2.handle, .snapshot, 400)) orelse |
| 1516 | return error.NoSnapshotOnCreate; | 1488 | return error.NoSnapshotOnCreate; |
| 1517 | f2.deinit(alloc); | 1489 | f2.deinit(alloc); |
| @@ -1521,9 +1493,8 @@ test "Server: a 1x1 attach joins but never creates" { | |||
| 1521 | // Refusing the creation must not cost the client its view: a terminal | 1493 | // Refusing the creation must not cost the client its view: a terminal |
| 1522 | // too small to spawn a session is still allowed to watch one, and may | 1494 | // too small to spawn a session is still allowed to watch one, and may |
| 1523 | // never be the reason one exists or the reason one resizes. | 1495 | // never be the reason one exists or the reason one resizes. |
| 1524 | const c3 = try std.net.connectUnixSocket(sock_path); | 1496 | const c3 = try dial.dialAttachNamed(sock_path, 1, 1, "tile"); |
| 1525 | defer c3.close(); | 1497 | defer c3.close(); |
| 1526 | try attachNamed(c3.handle, 1, 1, "tile"); | ||
| 1527 | const f3 = (try awaitFrame(alloc, &srv, c3.handle, .snapshot, 400)) orelse | 1498 | const f3 = (try awaitFrame(alloc, &srv, c3.handle, .snapshot, 400)) orelse |
| 1528 | return error.TinyJoinRefused; | 1499 | return error.TinyJoinRefused; |
| 1529 | f3.deinit(alloc); | 1500 | f3.deinit(alloc); |
| @@ -1551,17 +1522,15 @@ test "Server: attaching seats a client in activity order, and typing or resizing | |||
| 1551 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 1522 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 1552 | defer srv.deinit(); | 1523 | defer srv.deinit(); |
| 1553 | 1524 | ||
| 1554 | const ca = try std.net.connectUnixSocket(sock_path); | 1525 | const ca = try dial.dialAttachNamed(sock_path, 80, 24, ""); |
| 1555 | defer ca.close(); | 1526 | defer ca.close(); |
| 1556 | try attachNamed(ca.handle, 80, 24, ""); | ||
| 1557 | var spun: usize = 0; | 1527 | var spun: usize = 0; |
| 1558 | while (spun < 200 and srv.clients[0] == null) : (spun += 1) try srv.pumpOnce(5); | 1528 | while (spun < 200 and srv.clients[0] == null) : (spun += 1) try srv.pumpOnce(5); |
| 1559 | 1529 | ||
| 1560 | // Seated one at a time so the slot indices below are the attach order: | 1530 | // Seated one at a time so the slot indices below are the attach order: |
| 1561 | // freeClientSlot hands out the lowest free slot. | 1531 | // freeClientSlot hands out the lowest free slot. |
| 1562 | const cb = try std.net.connectUnixSocket(sock_path); | 1532 | const cb = try dial.dialAttachNamed(sock_path, 80, 24, ""); |
| 1563 | defer cb.close(); | 1533 | defer cb.close(); |
| 1564 | try attachNamed(cb.handle, 80, 24, ""); | ||
| 1565 | spun = 0; | 1534 | spun = 0; |
| 1566 | while (spun < 200 and srv.clients[1] == null) : (spun += 1) try srv.pumpOnce(5); | 1535 | while (spun < 200 and srv.clients[1] == null) : (spun += 1) try srv.pumpOnce(5); |
| 1567 | 1536 | ||
| @@ -1644,7 +1613,7 @@ test "Server: an observer that sends one byte does not stall the pump, and finis | |||
| 1644 | 1613 | ||
| 1645 | // Through the real listener, so the accept path seats it as the daemon | 1614 | // Through the real listener, so the accept path seats it as the daemon |
| 1646 | // would seat `mux d stats`. | 1615 | // would seat `mux d stats`. |
| 1647 | const obs = try std.net.connectUnixSocket(sock_path); | 1616 | const obs = try dial.dial(sock_path); |
| 1648 | defer obs.close(); | 1617 | defer obs.close(); |
| 1649 | try srv.pumpOnce(20); // accept | 1618 | try srv.pumpOnce(20); // accept |
| 1650 | try std.testing.expect(srv.observers[0] != null); | 1619 | try std.testing.expect(srv.observers[0] != null); |
| @@ -1667,7 +1636,7 @@ test "Server: an observer that sends one byte does not stall the pump, and finis | |||
| 1667 | 1636 | ||
| 1668 | // A second observer is served while the first is still mid-frame: the | 1637 | // A second observer is served while the first is still mid-frame: the |
| 1669 | // daemon is answering everyone, which is the whole claim. | 1638 | // daemon is answering everyone, which is the whole claim. |
| 1670 | const obs2 = try std.net.connectUnixSocket(sock_path); | 1639 | const obs2 = try dial.dial(sock_path); |
| 1671 | defer obs2.close(); | 1640 | defer obs2.close(); |
| 1672 | try proto.writeFrame(obs2.handle, .stats_req, ""); | 1641 | try proto.writeFrame(obs2.handle, .stats_req, ""); |
| 1673 | const r2 = (try awaitFrame(alloc, &srv, obs2.handle, .stats_reply, 100)) orelse | 1642 | const r2 = (try awaitFrame(alloc, &srv, obs2.handle, .stats_reply, 100)) orelse |
| @@ -1697,7 +1666,7 @@ test "Server: stalled observers are dropped on the idle deadline, and the table | |||
| 1697 | // at accept with nothing said. | 1666 | // at accept with nothing said. |
| 1698 | var peers: [srv_mod.max_observers]std.net.Stream = undefined; | 1667 | var peers: [srv_mod.max_observers]std.net.Stream = undefined; |
| 1699 | for (&peers) |*p| { | 1668 | for (&peers) |*p| { |
| 1700 | p.* = try std.net.connectUnixSocket(sock_path); | 1669 | p.* = try dial.dial(sock_path); |
| 1701 | try proto.writeAllFd(p.handle, &[_]u8{@intFromEnum(proto.MsgType.stats_req)}); | 1670 | try proto.writeAllFd(p.handle, &[_]u8{@intFromEnum(proto.MsgType.stats_req)}); |
| 1702 | try srv.pumpOnce(20); | 1671 | try srv.pumpOnce(20); |
| 1703 | } | 1672 | } |
| @@ -1717,7 +1686,7 @@ test "Server: stalled observers are dropped on the idle deadline, and the table | |||
| 1717 | var b: [1]u8 = undefined; | 1686 | var b: [1]u8 = undefined; |
| 1718 | try std.testing.expectEqual(@as(usize, 0), try std.posix.read(peers[0].handle, &b)); | 1687 | try std.testing.expectEqual(@as(usize, 0), try std.posix.read(peers[0].handle, &b)); |
| 1719 | 1688 | ||
| 1720 | const fresh = try std.net.connectUnixSocket(sock_path); | 1689 | const fresh = try dial.dial(sock_path); |
| 1721 | defer fresh.close(); | 1690 | defer fresh.close(); |
| 1722 | try proto.writeFrame(fresh.handle, .stats_req, ""); | 1691 | try proto.writeFrame(fresh.handle, .stats_req, ""); |
| 1723 | const r = (try awaitFrame(alloc, &srv, fresh.handle, .stats_reply, 100)) orelse | 1692 | const r = (try awaitFrame(alloc, &srv, fresh.handle, .stats_reply, 100)) orelse |
| @@ -1737,7 +1706,7 @@ test "Server: bytes after an attach in the same write reach the promoted client" | |||
| 1737 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); | 1706 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); |
| 1738 | defer srv.deinit(); | 1707 | defer srv.deinit(); |
| 1739 | 1708 | ||
| 1740 | const c = try std.net.connectUnixSocket(sock_path); | 1709 | const c = try dial.dial(sock_path); |
| 1741 | defer c.close(); | 1710 | defer c.close(); |
| 1742 | 1711 | ||
| 1743 | // attach + resize in ONE write: the resize lands in the observer's | 1712 | // attach + resize in ONE write: the resize lands in the observer's |
| @@ -1777,7 +1746,7 @@ test "Server: an observer that never reads its replies is dropped, not allowed t | |||
| 1777 | // reaper's — a nagging peer is never idle anyway. | 1746 | // reaper's — a nagging peer is never idle anyway. |
| 1778 | srv.observer_idle_ms = 60_000; | 1747 | srv.observer_idle_ms = 60_000; |
| 1779 | 1748 | ||
| 1780 | const peer = try std.net.connectUnixSocket(sock_path); | 1749 | const peer = try dial.dial(sock_path); |
| 1781 | defer peer.close(); | 1750 | defer peer.close(); |
| 1782 | try srv.pumpOnce(20); | 1751 | try srv.pumpOnce(20); |
| 1783 | // Every reply is up to sessions_text_len bytes and the peer never reads | 1752 | // Every reply is up to sessions_text_len bytes and the peer never reads |
| @@ -1816,7 +1785,7 @@ test "Server: a burst of observer frames is answered across pumps, never all in | |||
| 1816 | defer srv.deinit(); | 1785 | defer srv.deinit(); |
| 1817 | srv.observer_idle_ms = 60_000; | 1786 | srv.observer_idle_ms = 60_000; |
| 1818 | 1787 | ||
| 1819 | const peer = try std.net.connectUnixSocket(sock_path); | 1788 | const peer = try dial.dial(sock_path); |
| 1820 | defer peer.close(); | 1789 | defer peer.close(); |
| 1821 | try srv.pumpOnce(20); | 1790 | try srv.pumpOnce(20); |
| 1822 | 1791 | ||
| @@ -1871,7 +1840,7 @@ test "Server: the two bounded deadlines read a monotonic clock, not the calendar | |||
| 1871 | // observer on a step forwards. | 1840 | // observer on a step forwards. |
| 1872 | const wall_now = std.time.milliTimestamp(); | 1841 | const wall_now = std.time.milliTimestamp(); |
| 1873 | 1842 | ||
| 1874 | const obs = try std.net.connectUnixSocket(sock_path); | 1843 | const obs = try dial.dial(sock_path); |
| 1875 | defer obs.close(); | 1844 | defer obs.close(); |
| 1876 | try srv.pumpOnce(20); | 1845 | try srv.pumpOnce(20); |
| 1877 | try std.testing.expect(srv.observers[0] != null); | 1846 | try std.testing.expect(srv.observers[0] != null); |
| @@ -1899,7 +1868,7 @@ test "Server: an observer that dribbles a huge frame is dropped at the cap, not | |||
| 1899 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); | 1868 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); |
| 1900 | defer srv.deinit(); | 1869 | defer srv.deinit(); |
| 1901 | 1870 | ||
| 1902 | const peer = try std.net.connectUnixSocket(sock_path); | 1871 | const peer = try dial.dial(sock_path); |
| 1903 | defer peer.close(); | 1872 | defer peer.close(); |
| 1904 | try srv.pumpOnce(20); | 1873 | try srv.pumpOnce(20); |
| 1905 | try std.testing.expect(srv.observers[0] != null); | 1874 | try std.testing.expect(srv.observers[0] != null); |
src/server/server_test_await.zig
| Old | New | ||
|---|---|---|---|
| @@ -3,6 +3,7 @@ const proto = @import("term").protocol; | |||
| 3 | const shellint = @import("shellint.zig"); | 3 | const shellint = @import("shellint.zig"); |
| 4 | const TmpDir = @import("testtmp").TmpDir; | 4 | const TmpDir = @import("testtmp").TmpDir; |
| 5 | const h = @import("server_test_harness.zig"); | 5 | const h = @import("server_test_harness.zig"); |
| 6 | const dial = h.dial; | ||
| 6 | const srv_mod = @import("server.zig"); | 7 | const srv_mod = @import("server.zig"); |
| 7 | const Server = srv_mod.Server; | 8 | const Server = srv_mod.Server; |
| 8 | const attachNamed = h.attachNamed; | 9 | const attachNamed = h.attachNamed; |
| @@ -41,9 +42,8 @@ test "Server: OSC 133 marks reach attached clients as cmd_state pushes" { | |||
| 41 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 42 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 42 | defer srv.deinit(); | 43 | defer srv.deinit(); |
| 43 | 44 | ||
| 44 | const c = try std.net.connectUnixSocket(sock_path); | 45 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 45 | defer c.close(); | 46 | defer c.close(); |
| 46 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 47 | 47 | ||
| 48 | // Release the script into its C. The keystroke is echoed by the line | 48 | // Release the script into its C. The keystroke is echoed by the line |
| 49 | // discipline, so this pty chunk carries grid content as well as the | 49 | // discipline, so this pty chunk carries grid content as well as the |
| @@ -162,9 +162,8 @@ const IntegratedSession = struct { | |||
| 162 | try std.testing.expect(self.srv.shellint_dir != null); | 162 | try std.testing.expect(self.srv.shellint_dir != null); |
| 163 | try std.fs.cwd().access(self.srv.shellint_dir.?, .{}); | 163 | try std.fs.cwd().access(self.srv.shellint_dir.?, .{}); |
| 164 | 164 | ||
| 165 | self.conn = try std.net.connectUnixSocket(self.sock_path); | 165 | self.conn = try dial.dialAttach(self.sock_path, 80, 24); |
| 166 | errdefer self.conn.close(); | 166 | errdefer self.conn.close(); |
| 167 | try proto.writeFrame(self.conn.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 168 | return self; | 167 | return self; |
| 169 | } | 168 | } |
| 170 | 169 | ||
| @@ -377,9 +376,8 @@ test "Server: an entry already at the daemon's pid name costs neither the marks | |||
| 377 | 376 | ||
| 378 | // ...and the session actually works. A daemon that starts and then | 377 | // ...and the session actually works. A daemon that starts and then |
| 379 | // cannot answer would satisfy every assertion above. | 378 | // cannot answer would satisfy every assertion above. |
| 380 | const c = try std.net.connectUnixSocket(sock_path); | 379 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 381 | defer c.close(); | 380 | defer c.close(); |
| 382 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 383 | try proto.writeFrame(c.handle, .status_req, ""); | 381 | try proto.writeFrame(c.handle, .status_req, ""); |
| 384 | const f = (try awaitFrame(alloc, &srv, c.handle, .status_reply, 400)) orelse | 382 | const f = (try awaitFrame(alloc, &srv, c.handle, .status_reply, 400)) orelse |
| 385 | return error.NoStatusReply; | 383 | return error.NoStatusReply; |
| @@ -478,9 +476,8 @@ test "Server: an await is held open, answered by a mark, and re-answered immedia | |||
| 478 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 476 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 479 | defer srv.deinit(); | 477 | defer srv.deinit(); |
| 480 | 478 | ||
| 481 | const c = try std.net.connectUnixSocket(sock_path); | 479 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 482 | defer c.close(); | 480 | defer c.close(); |
| 483 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 484 | 481 | ||
| 485 | // since_seq is "what I already know about": only a return NEWER than this | 482 | // since_seq is "what I already know about": only a return NEWER than this |
| 486 | // may answer. Nothing has returned on this session at all. | 483 | // may answer. Nothing has returned on this session at all. |
| @@ -533,9 +530,8 @@ test "Server: an await is held open, answered by a mark, and re-answered immedia | |||
| 533 | // rather than learning nothing until the next transition. The EXIT CODE | 530 | // rather than learning nothing until the next transition. The EXIT CODE |
| 534 | // carries the news: an untouched tracker reports null, so 3 means this push | 531 | // carries the news: an untouched tracker reports null, so 3 means this push |
| 535 | // came from the command that ran. | 532 | // came from the command that ran. |
| 536 | const late = try std.net.connectUnixSocket(sock_path); | 533 | const late = try dial.dialAttach(sock_path, 80, 24); |
| 537 | defer late.close(); | 534 | defer late.close(); |
| 538 | try proto.writeFrame(late.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 539 | const f3 = (try awaitFrame(alloc, &srv, late.handle, .cmd_state, 200)) orelse | 535 | const f3 = (try awaitFrame(alloc, &srv, late.handle, .cmd_state, 200)) orelse |
| 540 | return error.NoCmdStateOnAttach; | 536 | return error.NoCmdStateOnAttach; |
| 541 | defer f3.deinit(alloc); | 537 | defer f3.deinit(alloc); |
| @@ -575,9 +571,8 @@ test "Server: a return is still answerable once the next command is running" { | |||
| 575 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 571 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 576 | defer srv.deinit(); | 572 | defer srv.deinit(); |
| 577 | 573 | ||
| 578 | const c = try std.net.connectUnixSocket(sock_path); | 574 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 579 | defer c.close(); | 575 | defer c.close(); |
| 580 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 581 | 576 | ||
| 582 | // Run the first command to completion, then start the second and wait | 577 | // Run the first command to completion, then start the second and wait |
| 583 | // until the daemon has actually seen it open. | 578 | // until the daemon has actually seen it open. |
| @@ -627,9 +622,8 @@ test "Server: an await with a settle floor is answered by output going quiet" { | |||
| 627 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 622 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 628 | defer srv.deinit(); | 623 | defer srv.deinit(); |
| 629 | 624 | ||
| 630 | const c = try std.net.connectUnixSocket(sock_path); | 625 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 631 | defer c.close(); | 626 | defer c.close(); |
| 632 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 633 | try proto.writeFrame(c.handle, .await_req, &proto.encodeAwaitReq(.{ | 627 | try proto.writeFrame(c.handle, .await_req, &proto.encodeAwaitReq(.{ |
| 634 | .since_seq = srv.sessions.table[0].?.tracker.seq, | 628 | .since_seq = srv.sessions.table[0].?.tracker.seq, |
| 635 | .settle_ms = 200, | 629 | .settle_ms = 200, |
| @@ -665,9 +659,8 @@ test "Server: an await with nothing to answer it ends at the bound the caller se | |||
| 665 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 659 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 666 | defer srv.deinit(); | 660 | defer srv.deinit(); |
| 667 | 661 | ||
| 668 | const c = try std.net.connectUnixSocket(sock_path); | 662 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 669 | defer c.close(); | 663 | defer c.close(); |
| 670 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 671 | // Nothing is typed at this session, no settle floor is asked for and no | 664 | // Nothing is typed at this session, no settle floor is asked for and no |
| 672 | // mark will ever come: the timeout is the only thing left that can end | 665 | // mark will ever come: the timeout is the only thing left that can end |
| 673 | // this wait, which is the point. | 666 | // this wait, which is the point. |
| @@ -718,9 +711,8 @@ test "Server: a timed-out await carries no exit code, not the last command's" { | |||
| 718 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 711 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 719 | defer srv.deinit(); | 712 | defer srv.deinit(); |
| 720 | 713 | ||
| 721 | const c = try std.net.connectUnixSocket(sock_path); | 714 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 722 | defer c.close(); | 715 | defer c.close(); |
| 723 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 724 | 716 | ||
| 725 | // First, collect the return honestly, exactly as an agent would. | 717 | // First, collect the return honestly, exactly as an agent would. |
| 726 | try proto.writeFrame(c.handle, .await_req, &proto.encodeAwaitReq(.{ | 718 | try proto.writeFrame(c.handle, .await_req, &proto.encodeAwaitReq(.{ |
| @@ -775,9 +767,8 @@ test "Server: without shell integration a foreground job's end is caught by the | |||
| 775 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); | 767 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); |
| 776 | defer srv.deinit(); | 768 | defer srv.deinit(); |
| 777 | 769 | ||
| 778 | const c = try std.net.connectUnixSocket(sock_path); | 770 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 779 | defer c.close(); | 771 | defer c.close(); |
| 780 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 781 | // sleep 2, not sleep 1: the edge has to SEE the pgid off the shell before | 772 | // sleep 2, not sleep 1: the edge has to SEE the pgid off the shell before |
| 782 | // "back on the shell" can mean anything, so the busy window must be wide | 773 | // "back on the shell" can mean anything, so the busy window must be wide |
| 783 | // enough to sample. A job too short to observe is one the settle floor is | 774 | // enough to sample. A job too short to observe is one the settle floor is |
| @@ -815,9 +806,8 @@ test "Server: re-attaching to another session drops the await it left behind" { | |||
| 815 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 806 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 816 | defer srv.deinit(); | 807 | defer srv.deinit(); |
| 817 | 808 | ||
| 818 | const c = try std.net.connectUnixSocket(sock_path); | 809 | const c = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 819 | defer c.close(); | 810 | defer c.close(); |
| 820 | try attachNamed(c.handle, 80, 24, "a"); | ||
| 821 | var spun: usize = 0; | 811 | var spun: usize = 0; |
| 822 | while (spun < 200 and srv.sessions.find("a") == null) : (spun += 1) try srv.pumpOnce(5); | 812 | while (spun < 200 and srv.sessions.find("a") == null) : (spun += 1) try srv.pumpOnce(5); |
| 823 | const si_a = srv.sessions.find("a") orelse return error.NoSessionA; | 813 | const si_a = srv.sessions.find("a") orelse return error.NoSessionA; |
| @@ -896,12 +886,10 @@ test "Server: cmd_state pushes stay inside their session" { | |||
| 896 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 886 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 897 | defer srv.deinit(); | 887 | defer srv.deinit(); |
| 898 | 888 | ||
| 899 | const a = try std.net.connectUnixSocket(sock_path); | 889 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 900 | defer a.close(); | 890 | defer a.close(); |
| 901 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | 891 | const b = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 902 | const b = try std.net.connectUnixSocket(sock_path); | ||
| 903 | defer b.close(); | 892 | defer b.close(); |
| 904 | try attachNamed(b.handle, 80, 24, "b"); | ||
| 905 | const fa = (try awaitFrame(alloc, &srv, a.handle, .snapshot, 400)) orelse | 893 | const fa = (try awaitFrame(alloc, &srv, a.handle, .snapshot, 400)) orelse |
| 906 | return error.NoSnapshotA; | 894 | return error.NoSnapshotA; |
| 907 | fa.deinit(alloc); | 895 | fa.deinit(alloc); |
| @@ -956,12 +944,10 @@ test "Server: an await resolves against the awaiting client's session" { | |||
| 956 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 944 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 957 | defer srv.deinit(); | 945 | defer srv.deinit(); |
| 958 | 946 | ||
| 959 | const a = try std.net.connectUnixSocket(sock_path); | 947 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 960 | defer a.close(); | 948 | defer a.close(); |
| 961 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | 949 | const b = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 962 | const b = try std.net.connectUnixSocket(sock_path); | ||
| 963 | defer b.close(); | 950 | defer b.close(); |
| 964 | try attachNamed(b.handle, 80, 24, "b"); | ||
| 965 | const fa = (try awaitFrame(alloc, &srv, a.handle, .snapshot, 400)) orelse | 951 | const fa = (try awaitFrame(alloc, &srv, a.handle, .snapshot, 400)) orelse |
| 966 | return error.NoSnapshotA; | 952 | return error.NoSnapshotA; |
| 967 | fa.deinit(alloc); | 953 | fa.deinit(alloc); |
src/server/server_test_clipboard.zig
| Old | New | ||
|---|---|---|---|
| @@ -3,11 +3,11 @@ const Engine = @import("term").engine.Engine; | |||
| 3 | const proto = @import("term").protocol; | 3 | const proto = @import("term").protocol; |
| 4 | const TmpDir = @import("testtmp").TmpDir; | 4 | const TmpDir = @import("testtmp").TmpDir; |
| 5 | const h = @import("server_test_harness.zig"); | 5 | const h = @import("server_test_harness.zig"); |
| 6 | const dial = h.dial; | ||
| 6 | const srv_mod = @import("server.zig"); | 7 | const srv_mod = @import("server.zig"); |
| 7 | const Server = srv_mod.Server; | 8 | const Server = srv_mod.Server; |
| 8 | const Session = srv_mod.Session; | 9 | const Session = srv_mod.Session; |
| 9 | const selectionReplyStatus = srv_mod.selectionReplyStatus; | 10 | const selectionReplyStatus = srv_mod.selectionReplyStatus; |
| 10 | const attachNamed = h.attachNamed; | ||
| 11 | const awaitFrame = h.awaitFrame; | 11 | const awaitFrame = h.awaitFrame; |
| 12 | const awaitGridText = h.awaitGridText; | 12 | const awaitGridText = h.awaitGridText; |
| 13 | const connectedPair = h.connectedPair; | 13 | const connectedPair = h.connectedPair; |
| @@ -123,9 +123,10 @@ test "Server: selection extraction statuses map to wire replies" { | |||
| 123 | // nothing at all. | 123 | // nothing at all. |
| 124 | // --------------------------------------------------------------------------- | 124 | // --------------------------------------------------------------------------- |
| 125 | 125 | ||
| 126 | // The layering forbids `engine` importing `protocol`, so the cap is written in | 126 | // `engine` does not import `protocol` — the VT engine holds no opinion about |
| 127 | // two places and this is what stops them drifting in silence. server.zig is one | 127 | // the wire format, and that convention is worth keeping — so the cap is written |
| 128 | // of the few modules that legitimately imports both. | 128 | // in two places and this is what stops them drifting in silence. server.zig is |
| 129 | // one of the few modules that legitimately imports both. | ||
| 129 | test "the engine's default clipboard cap is the wire's" { | 130 | test "the engine's default clipboard cap is the wire's" { |
| 130 | try std.testing.expectEqual( | 131 | try std.testing.expectEqual( |
| 131 | proto.clipboard_base64_max, | 132 | proto.clipboard_base64_max, |
| @@ -147,15 +148,13 @@ test "Server: a clipboard event reaches this session's clients and no others" { | |||
| 147 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 148 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 148 | defer srv.deinit(); | 149 | defer srv.deinit(); |
| 149 | 150 | ||
| 150 | const ca = try std.net.connectUnixSocket(sock_path); | 151 | const ca = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 151 | defer ca.close(); | 152 | defer ca.close(); |
| 152 | try attachNamed(ca.handle, 80, 24, "a"); | ||
| 153 | (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400) orelse | 153 | (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400) orelse |
| 154 | return error.NoSnapshotA).deinit(alloc); | 154 | return error.NoSnapshotA).deinit(alloc); |
| 155 | 155 | ||
| 156 | const cb = try std.net.connectUnixSocket(sock_path); | 156 | const cb = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 157 | defer cb.close(); | 157 | defer cb.close(); |
| 158 | try attachNamed(cb.handle, 80, 24, "b"); | ||
| 159 | (try awaitFrame(alloc, &srv, cb.handle, .snapshot, 400) orelse | 158 | (try awaitFrame(alloc, &srv, cb.handle, .snapshot, 400) orelse |
| 160 | return error.NoSnapshotB).deinit(alloc); | 159 | return error.NoSnapshotB).deinit(alloc); |
| 161 | 160 | ||
| @@ -235,8 +234,7 @@ fn clipboardIntoGap( | |||
| 235 | srv: *Server, | 234 | srv: *Server, |
| 236 | sock_path: []const u8, | 235 | sock_path: []const u8, |
| 237 | ) !GapWatermark { | 236 | ) !GapWatermark { |
| 238 | const a = try std.net.connectUnixSocket(sock_path); | 237 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 239 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 240 | (try awaitFrame(alloc, srv, a.handle, .snapshot, 400) orelse | 238 | (try awaitFrame(alloc, srv, a.handle, .snapshot, 400) orelse |
| 241 | return error.NoSnapshotBeforeGap).deinit(alloc); | 239 | return error.NoSnapshotBeforeGap).deinit(alloc); |
| 242 | 240 | ||
| @@ -363,7 +361,7 @@ test "Server: a delta reattach is replayed the gap's last clipboard set, then it | |||
| 363 | // departed client really held. Yank in vim, the link blinks, you are back | 361 | // departed client really held. Yank in vim, the link blinks, you are back |
| 364 | // two seconds later — losing the yank to that is how a copy feature | 362 | // two seconds later — losing the yank to that is how a copy feature |
| 365 | // becomes one you stop believing. | 363 | // becomes one you stop believing. |
| 366 | const b = try std.net.connectUnixSocket(sock_path); | 364 | const b = try dial.dial(sock_path); |
| 367 | defer b.close(); | 365 | defer b.close(); |
| 368 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(80, 24, wm.seq, wm.epoch)); | 366 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(80, 24, wm.seq, wm.epoch)); |
| 369 | 367 | ||
| @@ -426,7 +424,7 @@ test "Server: a reattach quoting the seq an event was stamped at is not replayed | |||
| 426 | try std.testing.expectEqual(@as(usize, Session.kinds.len), recorded); | 424 | try std.testing.expectEqual(@as(usize, Session.kinds.len), recorded); |
| 427 | try std.testing.expect(lowest > wm.seq); | 425 | try std.testing.expect(lowest > wm.seq); |
| 428 | 426 | ||
| 429 | const b = try std.net.connectUnixSocket(sock_path); | 427 | const b = try dial.dial(sock_path); |
| 430 | defer b.close(); | 428 | defer b.close(); |
| 431 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(80, 24, at, wm.epoch)); | 429 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(80, 24, at, wm.epoch)); |
| 432 | 430 | ||
| @@ -459,9 +457,8 @@ test "Server: a clipboard event in the gap is not replayed to a snapshot attach" | |||
| 459 | // seq 0, epoch 0: what a fresh client sends, and what `canServe` refuses | 457 | // seq 0, epoch 0: what a fresh client sends, and what `canServe` refuses |
| 460 | // by construction. This client has never seen this session, so the | 458 | // by construction. This client has never seen this session, so the |
| 461 | // clipboard write it slept through is not addressed to it. | 459 | // clipboard write it slept through is not addressed to it. |
| 462 | const b = try std.net.connectUnixSocket(sock_path); | 460 | const b = try dial.dialAttach(sock_path, 80, 24); |
| 463 | defer b.close(); | 461 | defer b.close(); |
| 464 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 465 | 462 | ||
| 466 | // `collectGapReplay` inspects EVERY frame: `awaitFrame` drops what it is not | 463 | // `collectGapReplay` inspects EVERY frame: `awaitFrame` drops what it is not |
| 467 | // looking for, so a replay queued ahead of the snapshot would be swallowed — | 464 | // looking for, so a replay queued ahead of the snapshot would be swallowed — |
| @@ -499,9 +496,8 @@ test "Server: a rebuild drops the pending clipboard it just put out of reach" { | |||
| 499 | 496 | ||
| 500 | // A joiner at a different size is the shortest route to a rebuild: it | 497 | // A joiner at a different size is the shortest route to a rebuild: it |
| 501 | // takes sendResync's size_changed arm straight into resyncSnapshot. | 498 | // takes sendResync's size_changed arm straight into resyncSnapshot. |
| 502 | const b = try std.net.connectUnixSocket(sock_path); | 499 | const b = try dial.dialAttach(sock_path, 100, 30); |
| 503 | defer b.close(); | 500 | defer b.close(); |
| 504 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(100, 30, 0, 0)); | ||
| 505 | (try awaitFrame(alloc, &srv, b.handle, .snapshot, 400) orelse | 501 | (try awaitFrame(alloc, &srv, b.handle, .snapshot, 400) orelse |
| 506 | return error.NoSnapshotAfterResize).deinit(alloc); | 502 | return error.NoSnapshotAfterResize).deinit(alloc); |
| 507 | 503 | ||
| @@ -600,9 +596,8 @@ test "Server: a session that dies holding a pending event frees it" { | |||
| 600 | // because `reap` does more for a session that HAS clients — queue | 596 | // because `reap` does more for a session that HAS clients — queue |
| 601 | // `exit_status`, drain, drop, then free — so attaching puts the free after | 597 | // `exit_status`, drain, drop, then free — so attaching puts the free after |
| 602 | // that sequence rather than after two no-op loops. | 598 | // that sequence rather than after two no-op loops. |
| 603 | const c = try std.net.connectUnixSocket(sock_path); | 599 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 604 | defer c.close(); | 600 | defer c.close(); |
| 605 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 606 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse | 601 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse |
| 607 | return error.NoSnapshot).deinit(alloc); | 602 | return error.NoSnapshot).deinit(alloc); |
| 608 | 603 | ||
src/server/server_test_deliver.zig
| Old | New | ||
|---|---|---|---|
| @@ -2,6 +2,7 @@ const std = @import("std"); | |||
| 2 | const proto = @import("term").protocol; | 2 | const proto = @import("term").protocol; |
| 3 | const TmpDir = @import("testtmp").TmpDir; | 3 | const TmpDir = @import("testtmp").TmpDir; |
| 4 | const h = @import("server_test_harness.zig"); | 4 | const h = @import("server_test_harness.zig"); |
| 5 | const dial = h.dial; | ||
| 5 | const srv_mod = @import("server.zig"); | 6 | const srv_mod = @import("server.zig"); |
| 6 | const Server = srv_mod.Server; | 7 | const Server = srv_mod.Server; |
| 7 | const connectedPair = h.connectedPair; | 8 | const connectedPair = h.connectedPair; |
| @@ -264,9 +265,8 @@ test "Server: the shell's exit status reaches an attached client" { | |||
| 264 | defer th.join(); | 265 | defer th.join(); |
| 265 | defer stop.store(true, .release); | 266 | defer stop.store(true, .release); |
| 266 | 267 | ||
| 267 | const c = try std.net.connectUnixSocket(sock_path); | 268 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 268 | defer c.close(); | 269 | defer c.close(); |
| 269 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 270 | 270 | ||
| 271 | // Settle first, so the shell is ready for input and the exit below is | 271 | // Settle first, so the shell is ready for input and the exit below is |
| 272 | // the next thing that happens. | 272 | // the next thing that happens. |
src/server/server_test_harness.zig
| Old | New | ||
|---|---|---|---|
| @@ -5,6 +5,11 @@ const replica_mod = @import("term").replica; | |||
| 5 | const quic = @import("quic"); | 5 | const quic = @import("quic"); |
| 6 | const quic_server = @import("quic_server.zig"); | 6 | const quic_server = @import("quic_server.zig"); |
| 7 | const TmpDir = @import("testtmp").TmpDir; | 7 | const TmpDir = @import("testtmp").TmpDir; |
| 8 | /// Re-exported so the eight sibling test files spell the module name once, | ||
| 9 | /// here, and reach it as `h.dial` — the same connect-a-daemon primitive the | ||
| 10 | /// CLI client and `mux a` dial through, so these tests hold the socket the | ||
| 11 | /// way the product does rather than hand-rolling the pair of calls. | ||
| 12 | pub const dial = @import("dial"); | ||
| 8 | const srv_mod = @import("server.zig"); | 13 | const srv_mod = @import("server.zig"); |
| 9 | const Server = srv_mod.Server; | 14 | const Server = srv_mod.Server; |
| 10 | 15 | ||
src/server/server_test_modes.zig
| Old | New | ||
|---|---|---|---|
| @@ -2,6 +2,7 @@ const std = @import("std"); | |||
| 2 | const proto = @import("term").protocol; | 2 | const proto = @import("term").protocol; |
| 3 | const TmpDir = @import("testtmp").TmpDir; | 3 | const TmpDir = @import("testtmp").TmpDir; |
| 4 | const h = @import("server_test_harness.zig"); | 4 | const h = @import("server_test_harness.zig"); |
| 5 | const dial = h.dial; | ||
| 5 | const srv_mod = @import("server.zig"); | 6 | const srv_mod = @import("server.zig"); |
| 6 | const Server = srv_mod.Server; | 7 | const Server = srv_mod.Server; |
| 7 | const awaitFrame = h.awaitFrame; | 8 | const awaitFrame = h.awaitFrame; |
| @@ -118,9 +119,8 @@ test "Server: the pty's mode bits reach a client on attach, and again only when | |||
| 118 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 119 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 119 | defer srv.deinit(); | 120 | defer srv.deinit(); |
| 120 | 121 | ||
| 121 | const c = try std.net.connectUnixSocket(sock_path); | 122 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 122 | defer c.close(); | 123 | defer c.close(); |
| 123 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 124 | 124 | ||
| 125 | var modes: std.ArrayList(proto.PtyModeFlags) = .empty; | 125 | var modes: std.ArrayList(proto.PtyModeFlags) = .empty; |
| 126 | defer modes.deinit(alloc); | 126 | defer modes.deinit(alloc); |
| @@ -190,9 +190,8 @@ test "Server: a BEL from the session reaches its client as a bell term_event" { | |||
| 190 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 190 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 191 | defer srv.deinit(); | 191 | defer srv.deinit(); |
| 192 | 192 | ||
| 193 | const c = try std.net.connectUnixSocket(sock_path); | 193 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 194 | defer c.close(); | 194 | defer c.close(); |
| 195 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 196 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse | 195 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse |
| 197 | return error.NoSnapshot).deinit(alloc); | 196 | return error.NoSnapshot).deinit(alloc); |
| 198 | 197 | ||
| @@ -243,9 +242,8 @@ test "Server: a burst of bells in one chunk is coalesced into one frame" { | |||
| 243 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 242 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 244 | defer srv.deinit(); | 243 | defer srv.deinit(); |
| 245 | 244 | ||
| 246 | const c = try std.net.connectUnixSocket(sock_path); | 245 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 247 | defer c.close(); | 246 | defer c.close(); |
| 248 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 249 | // Awaited, not assumed: this is what makes the ring below strictly later | 247 | // Awaited, not assumed: this is what makes the ring below strictly later |
| 250 | // than the attach, and so what removes the race described above. | 248 | // than the attach, and so what removes the race described above. |
| 251 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse | 249 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse |
| @@ -309,9 +307,8 @@ test "Server: a bell in a later chunk is its own frame, not folded into the firs | |||
| 309 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 307 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 310 | defer srv.deinit(); | 308 | defer srv.deinit(); |
| 311 | 309 | ||
| 312 | const c = try std.net.connectUnixSocket(sock_path); | 310 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 313 | defer c.close(); | 311 | defer c.close(); |
| 314 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 315 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse | 312 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse |
| 316 | return error.NoSnapshot).deinit(alloc); | 313 | return error.NoSnapshot).deinit(alloc); |
| 317 | 314 | ||
| @@ -361,9 +358,8 @@ test "Server: a session enabling bracketed paste tells its clients, and not agai | |||
| 361 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 358 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 362 | defer srv.deinit(); | 359 | defer srv.deinit(); |
| 363 | 360 | ||
| 364 | const c = try std.net.connectUnixSocket(sock_path); | 361 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 365 | defer c.close(); | 362 | defer c.close(); |
| 366 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 367 | 363 | ||
| 368 | // attach answers with the modes as they stand, which is before the shell | 364 | // attach answers with the modes as they stand, which is before the shell |
| 369 | // has run: keep pumping until one says the DECSET landed. Asserting on | 365 | // has run: keep pumping until one says the DECSET landed. Asserting on |
| @@ -429,9 +425,8 @@ test "Server: a client attaching to a session already in bracketed paste is told | |||
| 429 | // The first client is here to get the change SENT, not to observe it: | 425 | // The first client is here to get the change SENT, not to observe it: |
| 430 | // once term_modes_sent holds true, sampleTermModes will never fire again | 426 | // once term_modes_sent holds true, sampleTermModes will never fire again |
| 431 | // this session, so everything below can only be the resync's doing. | 427 | // this session, so everything below can only be the resync's doing. |
| 432 | const a = try std.net.connectUnixSocket(sock_path); | 428 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 433 | defer a.close(); | 429 | defer a.close(); |
| 434 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 435 | const enabled = while (try awaitFrame(alloc, &srv, a.handle, .term_modes, 400)) |f| { | 430 | const enabled = while (try awaitFrame(alloc, &srv, a.handle, .term_modes, 400)) |f| { |
| 436 | defer f.deinit(alloc); | 431 | defer f.deinit(alloc); |
| 437 | if ((try proto.decodeTermModes(f.payload)).bracketed_paste) break true; | 432 | if ((try proto.decodeTermModes(f.payload)).bracketed_paste) break true; |
| @@ -441,9 +436,8 @@ test "Server: a client attaching to a session already in bracketed paste is told | |||
| 441 | // Snapshot branch: a joiner holding nothing. It must be told what is | 436 | // Snapshot branch: a joiner holding nothing. It must be told what is |
| 442 | // true now, or it would paint a session whose application wants | 437 | // true now, or it would paint a session whose application wants |
| 443 | // bracketed paste while its host terminal has never heard of it. | 438 | // bracketed paste while its host terminal has never heard of it. |
| 444 | const b = try std.net.connectUnixSocket(sock_path); | 439 | const b = try dial.dialAttach(sock_path, 80, 24); |
| 445 | defer b.close(); | 440 | defer b.close(); |
| 446 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 447 | var snap = try modesWithResync(alloc, &srv, b.handle); | 441 | var snap = try modesWithResync(alloc, &srv, b.handle); |
| 448 | try std.testing.expectEqual(proto.MsgType.snapshot, snap.content orelse | 442 | try std.testing.expectEqual(proto.MsgType.snapshot, snap.content orelse |
| 449 | return error.NoContentFrameOnJoin); | 443 | return error.NoContentFrameOnJoin); |
| @@ -488,9 +482,8 @@ test "Server: a joiner that resizes the grid is still told the session's modes" | |||
| 488 | // As in the resync-branch test above: the first client is here to latch | 482 | // As in the resync-branch test above: the first client is here to latch |
| 489 | // term_modes_sent, after which sampleTermModes can never fire again this | 483 | // term_modes_sent, after which sampleTermModes can never fire again this |
| 490 | // session and everything below is the resync's doing alone. | 484 | // session and everything below is the resync's doing alone. |
| 491 | const a = try std.net.connectUnixSocket(sock_path); | 485 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 492 | defer a.close(); | 486 | defer a.close(); |
| 493 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 494 | const enabled = while (try awaitFrame(alloc, &srv, a.handle, .term_modes, 400)) |f| { | 487 | const enabled = while (try awaitFrame(alloc, &srv, a.handle, .term_modes, 400)) |f| { |
| 495 | defer f.deinit(alloc); | 488 | defer f.deinit(alloc); |
| 496 | if ((try proto.decodeTermModes(f.payload)).bracketed_paste) break true; | 489 | if ((try proto.decodeTermModes(f.payload)).bracketed_paste) break true; |
| @@ -501,9 +494,8 @@ test "Server: a joiner that resizes the grid is still told the session's modes" | |||
| 501 | // joiner at a DIFFERENT size returns early through `resyncSnapshot`. Left | 494 | // joiner at a DIFFERENT size returns early through `resyncSnapshot`. Left |
| 502 | // bare, bracketed paste stops being mirrored after any window resize, and | 495 | // bare, bracketed paste stops being mirrored after any window resize, and |
| 503 | // nothing connects a shell echoing a literal `200~` to the resize. | 496 | // nothing connects a shell echoing a literal `200~` to the resize. |
| 504 | const b = try std.net.connectUnixSocket(sock_path); | 497 | const b = try dial.dialAttach(sock_path, 100, 30); |
| 505 | defer b.close(); | 498 | defer b.close(); |
| 506 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(100, 30, 0, 0)); | ||
| 507 | 499 | ||
| 508 | // A's re-snapshot at B's size is the witness that this arm ran at all: | 500 | // A's re-snapshot at B's size is the witness that this arm ran at all: |
| 509 | // resyncSnapshot broadcasts, and the other two arms send to the joiner | 501 | // resyncSnapshot broadcasts, and the other two arms send to the joiner |
| @@ -568,9 +560,8 @@ test "Server: a window title reaches clients on change, and only on change" { | |||
| 568 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 560 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 569 | defer srv.deinit(); | 561 | defer srv.deinit(); |
| 570 | 562 | ||
| 571 | const c = try std.net.connectUnixSocket(sock_path); | 563 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 572 | defer c.close(); | 564 | defer c.close(); |
| 573 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 574 | 565 | ||
| 575 | // Queued behind the attach on the same socket, so the daemon has taken | 566 | // Queued behind the attach on the same socket, so the daemon has taken |
| 576 | // the attach before it can forward this — see the script's leading read. | 567 | // the attach before it can forward this — see the script's leading read. |
| @@ -642,9 +633,8 @@ test "Server: a session that never set a title has none sent for it" { | |||
| 642 | // Two attaches, because the two paths that could send an empty title are | 633 | // Two attaches, because the two paths that could send an empty title are |
| 643 | // different code: the SAMPLER and the RESYNC. An empty title on either makes | 634 | // different code: the SAMPLER and the RESYNC. An empty title on either makes |
| 644 | // the client wipe the title bar of a terminal whose session said nothing. | 635 | // the client wipe the title bar of a terminal whose session said nothing. |
| 645 | const a = try std.net.connectUnixSocket(sock_path); | 636 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 646 | defer a.close(); | 637 | defer a.close(); |
| 647 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 648 | 638 | ||
| 649 | var saw_content = false; | 639 | var saw_content = false; |
| 650 | var titles: usize = 0; | 640 | var titles: usize = 0; |
| @@ -658,8 +648,7 @@ test "Server: a session that never set a title has none sent for it" { | |||
| 658 | // The joiner goes in only once the shell's output has landed, so | 648 | // The joiner goes in only once the shell's output has landed, so |
| 659 | // its resync reads a session that has genuinely run. | 649 | // its resync reads a session that has genuinely run. |
| 660 | if (!joined) { | 650 | if (!joined) { |
| 661 | b = try std.net.connectUnixSocket(sock_path); | 651 | b = try dial.dialAttach(sock_path, 80, 24); |
| 662 | try proto.writeFrame(b.?.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 663 | joined = true; | 652 | joined = true; |
| 664 | } | 653 | } |
| 665 | after += 1; | 654 | after += 1; |
| @@ -718,9 +707,8 @@ test "Server: a joiner that resizes the grid is still told the session's title" | |||
| 718 | // The first client latches title_sent, after which sampleTermTitle can | 707 | // The first client latches title_sent, after which sampleTermTitle can |
| 719 | // never fire again for this title and everything below is the resync's | 708 | // never fire again for this title and everything below is the resync's |
| 720 | // doing alone. | 709 | // doing alone. |
| 721 | const a = try std.net.connectUnixSocket(sock_path); | 710 | const a = try dial.dialAttach(sock_path, 80, 24); |
| 722 | defer a.close(); | 711 | defer a.close(); |
| 723 | try proto.writeFrame(a.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 724 | const latched = (try awaitFrame(alloc, &srv, a.handle, .term_title, 400)) orelse | 712 | const latched = (try awaitFrame(alloc, &srv, a.handle, .term_title, 400)) orelse |
| 725 | return error.NoTitleFrame; | 713 | return error.NoTitleFrame; |
| 726 | defer latched.deinit(alloc); | 714 | defer latched.deinit(alloc); |
| @@ -730,9 +718,8 @@ test "Server: a joiner that resizes the grid is still told the session's title" | |||
| 730 | // through resyncSnapshot, before the delta/snapshot split. Left bare it | 718 | // through resyncSnapshot, before the delta/snapshot split. Left bare it |
| 731 | // is a real regression and a quiet one — the title bar would silently | 719 | // is a real regression and a quiet one — the title bar would silently |
| 732 | // stop matching the session after any window resize. | 720 | // stop matching the session after any window resize. |
| 733 | const b = try std.net.connectUnixSocket(sock_path); | 721 | const b = try dial.dialAttach(sock_path, 100, 30); |
| 734 | defer b.close(); | 722 | defer b.close(); |
| 735 | try proto.writeFrame(b.handle, .attach, &proto.encodeAttach(100, 30, 0, 0)); | ||
| 736 | 723 | ||
| 737 | // A's re-snapshot at B's size is the witness that this arm ran at all: | 724 | // A's re-snapshot at B's size is the witness that this arm ran at all: |
| 738 | // resyncSnapshot broadcasts, and the other two arms send to the joiner | 725 | // resyncSnapshot broadcasts, and the other two arms send to the joiner |
src/server/server_test_session.zig
| Old | New | ||
|---|---|---|---|
| @@ -6,6 +6,7 @@ const quic_server = @import("quic_server.zig"); | |||
| 6 | const xdg = @import("xdg"); | 6 | const xdg = @import("xdg"); |
| 7 | const TmpDir = @import("testtmp").TmpDir; | 7 | const TmpDir = @import("testtmp").TmpDir; |
| 8 | const h = @import("server_test_harness.zig"); | 8 | const h = @import("server_test_harness.zig"); |
| 9 | const dial = h.dial; | ||
| 9 | const srv_mod = @import("server.zig"); | 10 | const srv_mod = @import("server.zig"); |
| 10 | const SessionTable = @import("server_sessions.zig").SessionTable; | 11 | const SessionTable = @import("server_sessions.zig").SessionTable; |
| 11 | const Server = srv_mod.Server; | 12 | const Server = srv_mod.Server; |
| @@ -13,7 +14,6 @@ const boundUdpPort = srv_mod.boundUdpPort; | |||
| 13 | const max_sessions = srv_mod.max_sessions; | 14 | const max_sessions = srv_mod.max_sessions; |
| 14 | const shutdown_flag = &srv_mod.shutdown_flag; | 15 | const shutdown_flag = &srv_mod.shutdown_flag; |
| 15 | const applyFrame = h.applyFrame; | 16 | const applyFrame = h.applyFrame; |
| 16 | const attachNamed = h.attachNamed; | ||
| 17 | const awaitFrame = h.awaitFrame; | 17 | const awaitFrame = h.awaitFrame; |
| 18 | const connectedPair = h.connectedPair; | 18 | const connectedPair = h.connectedPair; |
| 19 | const firstStateFrame = h.firstStateFrame; | 19 | const firstStateFrame = h.firstStateFrame; |
| @@ -64,9 +64,8 @@ test "Server: a second daemon refuses a live socket instead of stealing it" { | |||
| 64 | // ...and it still reaches the daemon that was already there. The epoch | 64 | // ...and it still reaches the daemon that was already there. The epoch |
| 65 | // is what makes that precise: it names one daemon *instance*, so | 65 | // is what makes that precise: it names one daemon *instance*, so |
| 66 | // matching it rules out having been handed a replacement. | 66 | // matching it rules out having been handed a replacement. |
| 67 | const c = try std.net.connectUnixSocket(sock_path); | 67 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 68 | defer c.close(); | 68 | defer c.close(); |
| 69 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 70 | const first = try firstStateFrame(alloc, c.handle, 10_000); | 69 | const first = try firstStateFrame(alloc, c.handle, 10_000); |
| 71 | try std.testing.expect(first != null); | 70 | try std.testing.expect(first != null); |
| 72 | try std.testing.expectEqual(proto.MsgType.snapshot, first.?.type); | 71 | try std.testing.expectEqual(proto.MsgType.snapshot, first.?.type); |
| @@ -102,9 +101,8 @@ test "Server: a dead daemon's leftover socket file is cleared and rebound" { | |||
| 102 | defer stop.store(true, .release); | 101 | defer stop.store(true, .release); |
| 103 | 102 | ||
| 104 | // And the rebind is real, not just a file that reappeared: it serves. | 103 | // And the rebind is real, not just a file that reappeared: it serves. |
| 105 | const c = try std.net.connectUnixSocket(sock_path); | 104 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 106 | defer c.close(); | 105 | defer c.close(); |
| 107 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 108 | const first = try firstStateFrame(alloc, c.handle, 10_000); | 106 | const first = try firstStateFrame(alloc, c.handle, 10_000); |
| 109 | try std.testing.expect(first != null); | 107 | try std.testing.expect(first != null); |
| 110 | try std.testing.expectEqual(srv.sessions.table[0].?.epoch, first.?.epoch); | 108 | try std.testing.expectEqual(srv.sessions.table[0].?.epoch, first.?.epoch); |
| @@ -369,7 +367,7 @@ test "Server: stop_req from a bare connection requests shutdown; run returns 0" | |||
| 369 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); | 367 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); |
| 370 | defer srv.deinit(); | 368 | defer srv.deinit(); |
| 371 | 369 | ||
| 372 | const c = try std.net.connectUnixSocket(sock_path); | 370 | const c = try dial.dial(sock_path); |
| 373 | defer c.close(); | 371 | defer c.close(); |
| 374 | // No attach first: the frame must be honored from the OBSERVER | 372 | // No attach first: the frame must be honored from the OBSERVER |
| 375 | // dispatch, which is where a bare `mux d stop` connection lives. | 373 | // dispatch, which is where a bare `mux d stop` connection lives. |
| @@ -404,9 +402,8 @@ test "Server: stop_req from an attached client is honored too" { | |||
| 404 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); | 402 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); |
| 405 | defer srv.deinit(); | 403 | defer srv.deinit(); |
| 406 | 404 | ||
| 407 | const c = try std.net.connectUnixSocket(sock_path); | 405 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 408 | defer c.close(); | 406 | defer c.close(); |
| 409 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 410 | // Both frames go out before any pump and the stop still lands in | 407 | // Both frames go out before any pump and the stop still lands in |
| 411 | // `handleFrame`: one observer read takes both into the buffer, the attach | 408 | // `handleFrame`: one observer read takes both into the buffer, the attach |
| 412 | // promotes the connection with that buffer, and the stop drains as the | 409 | // promotes the connection with that buffer, and the stop drains as the |
| @@ -450,7 +447,7 @@ test "Server: endpoint_req binds a listener lazily, answers the same port on bot | |||
| 450 | // keeps this half from depending on the test machine having a real | 447 | // keeps this half from depending on the test machine having a real |
| 451 | // ~/.config/mux/key. The observer arm is the load-bearing one: | 448 | // ~/.config/mux/key. The observer arm is the load-bearing one: |
| 452 | // `mux d endpoint` never attaches. | 449 | // `mux d endpoint` never attaches. |
| 453 | const obs = try std.net.connectUnixSocket(sock_path); | 450 | const obs = try dial.dial(sock_path); |
| 454 | defer obs.close(); | 451 | defer obs.close(); |
| 455 | try proto.writeFrame(obs.handle, .endpoint_req, ""); | 452 | try proto.writeFrame(obs.handle, .endpoint_req, ""); |
| 456 | const reply = (try awaitFrame(alloc, &srv, obs.handle, .endpoint_reply, 200)) orelse | 453 | const reply = (try awaitFrame(alloc, &srv, obs.handle, .endpoint_reply, 200)) orelse |
| @@ -469,9 +466,8 @@ test "Server: endpoint_req binds a listener lazily, answers the same port on bot | |||
| 469 | 466 | ||
| 470 | // The attached-client arm answers the same verb the same way, on a | 467 | // The attached-client arm answers the same verb the same way, on a |
| 471 | // separate connection that attaches first. | 468 | // separate connection that attaches first. |
| 472 | const cl = try std.net.connectUnixSocket(sock_path); | 469 | const cl = try dial.dialAttach(sock_path, 80, 24); |
| 473 | defer cl.close(); | 470 | defer cl.close(); |
| 474 | try proto.writeFrame(cl.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 475 | try proto.writeFrame(cl.handle, .endpoint_req, ""); | 471 | try proto.writeFrame(cl.handle, .endpoint_req, ""); |
| 476 | const reply3 = (try awaitFrame(alloc, &srv, cl.handle, .endpoint_reply, 400)) orelse | 472 | const reply3 = (try awaitFrame(alloc, &srv, cl.handle, .endpoint_reply, 400)) orelse |
| 477 | return error.NoAttachedEndpointReply; | 473 | return error.NoAttachedEndpointReply; |
| @@ -539,9 +535,8 @@ test "Server: status_req is answered on an attached client and on a bare observe | |||
| 539 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 535 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 540 | defer srv.deinit(); | 536 | defer srv.deinit(); |
| 541 | 537 | ||
| 542 | const c = try std.net.connectUnixSocket(sock_path); | 538 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 543 | defer c.close(); | 539 | defer c.close(); |
| 544 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 545 | try proto.writeFrame(c.handle, .status_req, ""); | 540 | try proto.writeFrame(c.handle, .status_req, ""); |
| 546 | const f = (try awaitFrame(alloc, &srv, c.handle, .status_reply, 400)) orelse | 541 | const f = (try awaitFrame(alloc, &srv, c.handle, .status_reply, 400)) orelse |
| 547 | return error.NoAttachedStatusReply; | 542 | return error.NoAttachedStatusReply; |
| @@ -559,7 +554,7 @@ test "Server: status_req is answered on an attached client and on a bare observe | |||
| 559 | // `mux a status` never attaches, so the observer arm is the load-bearing | 554 | // `mux a status` never attaches, so the observer arm is the load-bearing |
| 560 | // one — same reasoning as endpoint_req's, and the same failure if it is | 555 | // one — same reasoning as endpoint_req's, and the same failure if it is |
| 561 | // missing: the frame falls into `else => {}` and the caller hangs. | 556 | // missing: the frame falls into `else => {}` and the caller hangs. |
| 562 | const obs = try std.net.connectUnixSocket(sock_path); | 557 | const obs = try dial.dial(sock_path); |
| 563 | defer obs.close(); | 558 | defer obs.close(); |
| 564 | try proto.writeFrame(obs.handle, .status_req, ""); | 559 | try proto.writeFrame(obs.handle, .status_req, ""); |
| 565 | const f2 = (try awaitFrame(alloc, &srv, obs.handle, .status_reply, 400)) orelse | 560 | const f2 = (try awaitFrame(alloc, &srv, obs.handle, .status_reply, 400)) orelse |
| @@ -611,12 +606,10 @@ test "Server: two named sessions hold two shells with independent content" { | |||
| 611 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 606 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 612 | defer srv.deinit(); | 607 | defer srv.deinit(); |
| 613 | 608 | ||
| 614 | const ca = try std.net.connectUnixSocket(sock_path); | 609 | const ca = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 615 | defer ca.close(); | 610 | defer ca.close(); |
| 616 | try attachNamed(ca.handle, 80, 24, "a"); | 611 | const cb = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 617 | const cb = try std.net.connectUnixSocket(sock_path); | ||
| 618 | defer cb.close(); | 612 | defer cb.close(); |
| 619 | try attachNamed(cb.handle, 80, 24, "b"); | ||
| 620 | 613 | ||
| 621 | try proto.writeFrame(ca.handle, .input, "MARKER-ALPHA\n"); | 614 | try proto.writeFrame(ca.handle, .input, "MARKER-ALPHA\n"); |
| 622 | try proto.writeFrame(cb.handle, .input, "MARKER-BETA\n"); | 615 | try proto.writeFrame(cb.handle, .input, "MARKER-BETA\n"); |
| @@ -710,12 +703,10 @@ test "Server: every session shell is told the socket it lives on and its own nam | |||
| 710 | ); | 703 | ); |
| 711 | defer alloc.free(probe); | 704 | defer alloc.free(probe); |
| 712 | 705 | ||
| 713 | const c0 = try std.net.connectUnixSocket(sock_path); | 706 | const c0 = try dial.dialAttachNamed(sock_path, 80, 24, ""); |
| 714 | defer c0.close(); | 707 | defer c0.close(); |
| 715 | try attachNamed(c0.handle, 80, 24, ""); | 708 | const ca = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 716 | const ca = try std.net.connectUnixSocket(sock_path); | ||
| 717 | defer ca.close(); | 709 | defer ca.close(); |
| 718 | try attachNamed(ca.handle, 80, 24, "a"); | ||
| 719 | 710 | ||
| 720 | try proto.writeFrame(c0.handle, .input, probe); | 711 | try proto.writeFrame(c0.handle, .input, probe); |
| 721 | try proto.writeFrame(ca.handle, .input, probe); | 712 | try proto.writeFrame(ca.handle, .input, probe); |
| @@ -751,9 +742,8 @@ test "Server: a bare 20-byte attach lands in the default session" { | |||
| 751 | // tail at all. An | 742 | // tail at all. An |
| 752 | // old client must land in the default session, not create a nameless | 743 | // old client must land in the default session, not create a nameless |
| 753 | // one beside it. | 744 | // one beside it. |
| 754 | const c = try std.net.connectUnixSocket(sock_path); | 745 | const c = try dial.dialAttach(sock_path, 80, 24); |
| 755 | defer c.close(); | 746 | defer c.close(); |
| 756 | try proto.writeFrame(c.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | ||
| 757 | const f = (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400)) orelse | 747 | const f = (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400)) orelse |
| 758 | return error.NoSnapshotOnBareAttach; | 748 | return error.NoSnapshotOnBareAttach; |
| 759 | f.deinit(alloc); | 749 | f.deinit(alloc); |
| @@ -783,9 +773,8 @@ test "Server: an attach past max_sessions is refused with exit_status, sessions | |||
| 783 | for (1..max_sessions) |i| { | 773 | for (1..max_sessions) |i| { |
| 784 | var nb: [8]u8 = undefined; | 774 | var nb: [8]u8 = undefined; |
| 785 | const nm = try std.fmt.bufPrint(&nb, "s{d}", .{i}); | 775 | const nm = try std.fmt.bufPrint(&nb, "s{d}", .{i}); |
| 786 | const c = try std.net.connectUnixSocket(sock_path); | 776 | const c = try dial.dialAttachNamed(sock_path, 80, 24, nm); |
| 787 | defer c.close(); | 777 | defer c.close(); |
| 788 | try attachNamed(c.handle, 80, 24, nm); | ||
| 789 | const f = (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400)) orelse | 778 | const f = (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400)) orelse |
| 790 | return error.NoSnapshotFillingTable; | 779 | return error.NoSnapshotFillingTable; |
| 791 | f.deinit(alloc); | 780 | f.deinit(alloc); |
| @@ -797,9 +786,8 @@ test "Server: an attach past max_sessions is refused with exit_status, sessions | |||
| 797 | 786 | ||
| 798 | // One name past the table gets the same honest no a full client table | 787 | // One name past the table gets the same honest no a full client table |
| 799 | // gives. | 788 | // gives. |
| 800 | const extra = try std.net.connectUnixSocket(sock_path); | 789 | const extra = try dial.dialAttachNamed(sock_path, 80, 24, "extra"); |
| 801 | defer extra.close(); | 790 | defer extra.close(); |
| 802 | try attachNamed(extra.handle, 80, 24, "extra"); | ||
| 803 | const f = (try awaitFrame(alloc, &srv, extra.handle, .exit_status, 400)) orelse | 791 | const f = (try awaitFrame(alloc, &srv, extra.handle, .exit_status, 400)) orelse |
| 804 | return error.NoRefusal; | 792 | return error.NoRefusal; |
| 805 | defer f.deinit(alloc); | 793 | defer f.deinit(alloc); |
| @@ -841,9 +829,8 @@ test "Server: a table of TERM-ignoring shells costs one grace, not one each" { | |||
| 841 | for (1..9) |i| { | 829 | for (1..9) |i| { |
| 842 | var nb: [8]u8 = undefined; | 830 | var nb: [8]u8 = undefined; |
| 843 | const nm = try std.fmt.bufPrint(&nb, "s{d}", .{i}); | 831 | const nm = try std.fmt.bufPrint(&nb, "s{d}", .{i}); |
| 844 | const c = try std.net.connectUnixSocket(sock_path); | 832 | const c = try dial.dialAttachNamed(sock_path, 80, 24, nm); |
| 845 | defer c.close(); | 833 | defer c.close(); |
| 846 | try attachNamed(c.handle, 80, 24, nm); | ||
| 847 | const f = (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400)) orelse | 834 | const f = (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400)) orelse |
| 848 | return error.NoSnapshotFillingTable; | 835 | return error.NoSnapshotFillingTable; |
| 849 | f.deinit(alloc); | 836 | f.deinit(alloc); |
| @@ -898,9 +885,8 @@ test "Server: an invalid name on the wire is refused, not created" { | |||
| 898 | 885 | ||
| 899 | // A raw-wire client can spell any bytes it likes; a name no tool could | 886 | // A raw-wire client can spell any bytes it likes; a name no tool could |
| 900 | // ever address must die here, not become a session. | 887 | // ever address must die here, not become a session. |
| 901 | const c = try std.net.connectUnixSocket(sock_path); | 888 | const c = try dial.dialAttachNamed(sock_path, 80, 24, "has space"); |
| 902 | defer c.close(); | 889 | defer c.close(); |
| 903 | try attachNamed(c.handle, 80, 24, "has space"); | ||
| 904 | const f = (try awaitFrame(alloc, &srv, c.handle, .exit_status, 400)) orelse | 890 | const f = (try awaitFrame(alloc, &srv, c.handle, .exit_status, 400)) orelse |
| 905 | return error.NoRefusal; | 891 | return error.NoRefusal; |
| 906 | defer f.deinit(alloc); | 892 | defer f.deinit(alloc); |
| @@ -960,17 +946,15 @@ test "Server: one session's shell exiting drops only its clients; the daemon car | |||
| 960 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 946 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 961 | defer srv.deinit(); | 947 | defer srv.deinit(); |
| 962 | 948 | ||
| 963 | const ca = try std.net.connectUnixSocket(sock_path); | 949 | const ca = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 964 | defer ca.close(); | 950 | defer ca.close(); |
| 965 | try attachNamed(ca.handle, 80, 24, "a"); | ||
| 966 | const fa = (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400)) orelse | 951 | const fa = (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400)) orelse |
| 967 | return error.NoSnapshotA; | 952 | return error.NoSnapshotA; |
| 968 | fa.deinit(alloc); | 953 | fa.deinit(alloc); |
| 969 | const si_a = srv.sessions.find("a") orelse return error.SessionAMissing; | 954 | const si_a = srv.sessions.find("a") orelse return error.SessionAMissing; |
| 970 | 955 | ||
| 971 | const cb = try std.net.connectUnixSocket(sock_path); | 956 | const cb = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 972 | defer cb.close(); | 957 | defer cb.close(); |
| 973 | try attachNamed(cb.handle, 80, 24, "b"); | ||
| 974 | 958 | ||
| 975 | // b's liveness established BEFORE a dies, so its survival below is a | 959 | // b's liveness established BEFORE a dies, so its survival below is a |
| 976 | // comparison and not a hope. | 960 | // comparison and not a hope. |
| @@ -1062,7 +1046,7 @@ fn awaitFrameThreaded( | |||
| 1062 | /// stays buffered, so a shared socket lets a later question be answered by an | 1046 | /// stays buffered, so a shared socket lets a later question be answered by an |
| 1063 | /// earlier one's frame — generated before the very event being waited on. | 1047 | /// earlier one's frame — generated before the very event being waited on. |
| 1064 | fn waitStats(alloc: std.mem.Allocator, sock_path: []const u8, want: []const u8) !void { | 1048 | fn waitStats(alloc: std.mem.Allocator, sock_path: []const u8, want: []const u8) !void { |
| 1065 | const obs = try std.net.connectUnixSocket(sock_path); | 1049 | const obs = try dial.dial(sock_path); |
| 1066 | defer obs.close(); | 1050 | defer obs.close(); |
| 1067 | var tries: usize = 0; | 1051 | var tries: usize = 0; |
| 1068 | while (tries < 40) : (tries += 1) { | 1052 | while (tries < 40) : (tries += 1) { |
| @@ -1083,9 +1067,8 @@ fn probeEmptiedDaemon(alloc: std.mem.Allocator, sock_path: []const u8) !void { | |||
| 1083 | // only one, so the table empties behind it. 7 rather than 0 so the | 1067 | // only one, so the table empties behind it. 7 rather than 0 so the |
| 1084 | // caller's exit-code pin can tell the two contracts apart: under the | 1068 | // caller's exit-code pin can tell the two contracts apart: under the |
| 1085 | // retired one this shell's code WAS the daemon's. | 1069 | // retired one this shell's code WAS the daemon's. |
| 1086 | const c1 = try std.net.connectUnixSocket(sock_path); | 1070 | const c1 = try dial.dialAttachNamed(sock_path, 80, 24, proto.default_session); |
| 1087 | defer c1.close(); | 1071 | defer c1.close(); |
| 1088 | try attachNamed(c1.handle, 80, 24, proto.default_session); | ||
| 1089 | const snap = (try awaitFrameThreaded(alloc, c1.handle, .snapshot, 4000)) orelse | 1072 | const snap = (try awaitFrameThreaded(alloc, c1.handle, .snapshot, 4000)) orelse |
| 1090 | return error.NoFirstSnapshot; | 1073 | return error.NoFirstSnapshot; |
| 1091 | snap.deinit(alloc); | 1074 | snap.deinit(alloc); |
| @@ -1096,9 +1079,8 @@ fn probeEmptiedDaemon(alloc: std.mem.Allocator, sock_path: []const u8) !void { | |||
| 1096 | // Still serving with nothing to serve: a birth on the emptied daemon | 1079 | // Still serving with nothing to serve: a birth on the emptied daemon |
| 1097 | // takes the default name back, which is the attach `mux --sock PATH` | 1080 | // takes the default name back, which is the attach `mux --sock PATH` |
| 1098 | // sends. | 1081 | // sends. |
| 1099 | const c2 = try std.net.connectUnixSocket(sock_path); | 1082 | const c2 = try dial.dialAttachNamed(sock_path, 80, 24, proto.default_session); |
| 1100 | defer c2.close(); | 1083 | defer c2.close(); |
| 1101 | try attachNamed(c2.handle, 80, 24, proto.default_session); | ||
| 1102 | const reborn = (try awaitFrameThreaded(alloc, c2.handle, .snapshot, 4000)) orelse | 1084 | const reborn = (try awaitFrameThreaded(alloc, c2.handle, .snapshot, 4000)) orelse |
| 1103 | return error.EmptyDaemonRefusedABirth; | 1085 | return error.EmptyDaemonRefusedABirth; |
| 1104 | reborn.deinit(alloc); | 1086 | reborn.deinit(alloc); |
| @@ -1162,9 +1144,8 @@ test "Server: a dead name re-attaches as a fresh session with a new epoch" { | |||
| 1162 | // First instance of "a": hold on to what its attach answered — the | 1144 | // First instance of "a": hold on to what its attach answered — the |
| 1163 | // epoch its snapshots were stamped with, and the newest seq counted | 1145 | // epoch its snapshots were stamped with, and the newest seq counted |
| 1164 | // under it. That pair is exactly what a surviving client would quote. | 1146 | // under it. That pair is exactly what a surviving client would quote. |
| 1165 | const c1 = try std.net.connectUnixSocket(sock_path); | 1147 | const c1 = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 1166 | defer c1.close(); | 1148 | defer c1.close(); |
| 1167 | try attachNamed(c1.handle, 80, 24, "a"); | ||
| 1168 | const f1 = (try awaitFrame(alloc, &srv, c1.handle, .snapshot, 400)) orelse | 1149 | const f1 = (try awaitFrame(alloc, &srv, c1.handle, .snapshot, 400)) orelse |
| 1169 | return error.NoFirstSnapshot; | 1150 | return error.NoFirstSnapshot; |
| 1170 | defer f1.deinit(alloc); | 1151 | defer f1.deinit(alloc); |
| @@ -1182,7 +1163,7 @@ test "Server: a dead name re-attaches as a fresh session with a new epoch" { | |||
| 1182 | // Re-attach the dead name quoting the DEAD instance's seq and epoch — the | 1163 | // Re-attach the dead name quoting the DEAD instance's seq and epoch — the |
| 1183 | // reconnect a client that missed the death sends. A fresh session cannot | 1164 | // reconnect a client that missed the death sends. A fresh session cannot |
| 1184 | // delta-serve seqs counted by a shell it never was. | 1165 | // delta-serve seqs counted by a shell it never was. |
| 1185 | const c2 = try std.net.connectUnixSocket(sock_path); | 1166 | const c2 = try dial.dial(sock_path); |
| 1186 | defer c2.close(); | 1167 | defer c2.close(); |
| 1187 | var abuf: [proto.attach_max_len]u8 = undefined; | 1168 | var abuf: [proto.attach_max_len]u8 = undefined; |
| 1188 | try proto.writeFrame( | 1169 | try proto.writeFrame( |
| @@ -1229,12 +1210,10 @@ test "Server: dump names a session; an unknown name answers in words" { | |||
| 1229 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 1210 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 1230 | defer srv.deinit(); | 1211 | defer srv.deinit(); |
| 1231 | 1212 | ||
| 1232 | const ca = try std.net.connectUnixSocket(sock_path); | 1213 | const ca = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 1233 | defer ca.close(); | 1214 | defer ca.close(); |
| 1234 | try attachNamed(ca.handle, 80, 24, "a"); | 1215 | const cb = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 1235 | const cb = try std.net.connectUnixSocket(sock_path); | ||
| 1236 | defer cb.close(); | 1216 | defer cb.close(); |
| 1237 | try attachNamed(cb.handle, 80, 24, "b"); | ||
| 1238 | 1217 | ||
| 1239 | try proto.writeFrame(ca.handle, .input, "MARKER-ALPHA\n"); | 1218 | try proto.writeFrame(ca.handle, .input, "MARKER-ALPHA\n"); |
| 1240 | try proto.writeFrame(cb.handle, .input, "MARKER-BETA\n"); | 1219 | try proto.writeFrame(cb.handle, .input, "MARKER-BETA\n"); |
| @@ -1297,12 +1276,10 @@ test "Server: an observer's status_req names a session by tail" { | |||
| 1297 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 1276 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 1298 | defer srv.deinit(); | 1277 | defer srv.deinit(); |
| 1299 | 1278 | ||
| 1300 | const a = try std.net.connectUnixSocket(sock_path); | 1279 | const a = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 1301 | defer a.close(); | 1280 | defer a.close(); |
| 1302 | try attachNamed(a.handle, 80, 24, "a"); | 1281 | const b = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 1303 | const b = try std.net.connectUnixSocket(sock_path); | ||
| 1304 | defer b.close(); | 1282 | defer b.close(); |
| 1305 | try attachNamed(b.handle, 80, 24, "b"); | ||
| 1306 | const fa = (try awaitFrame(alloc, &srv, a.handle, .snapshot, 400)) orelse | 1283 | const fa = (try awaitFrame(alloc, &srv, a.handle, .snapshot, 400)) orelse |
| 1307 | return error.NoSnapshotA; | 1284 | return error.NoSnapshotA; |
| 1308 | fa.deinit(alloc); | 1285 | fa.deinit(alloc); |
| @@ -1319,7 +1296,7 @@ test "Server: an observer's status_req names a session by tail" { | |||
| 1319 | 1296 | ||
| 1320 | // A bare connection — an observer, never attached — asks about b by | 1297 | // A bare connection — an observer, never attached — asks about b by |
| 1321 | // name alone. | 1298 | // name alone. |
| 1322 | const obs = try std.net.connectUnixSocket(sock_path); | 1299 | const obs = try dial.dial(sock_path); |
| 1323 | defer obs.close(); | 1300 | defer obs.close(); |
| 1324 | try proto.writeFrame(obs.handle, .status_req, "b"); | 1301 | try proto.writeFrame(obs.handle, .status_req, "b"); |
| 1325 | const reply = (try awaitFrame(alloc, &srv, obs.handle, .status_reply, 400)) orelse | 1302 | const reply = (try awaitFrame(alloc, &srv, obs.handle, .status_reply, 400)) orelse |
| @@ -1366,12 +1343,10 @@ test "Server: stats names every live session" { | |||
| 1366 | 1343 | ||
| 1367 | // The default session (bare attach) plus one named session: two live | 1344 | // The default session (bare attach) plus one named session: two live |
| 1368 | // sessions total, both of which must show up by name. | 1345 | // sessions total, both of which must show up by name. |
| 1369 | const ca = try std.net.connectUnixSocket(sock_path); | 1346 | const ca = try dial.dialAttach(sock_path, 80, 24); |
| 1370 | defer ca.close(); | 1347 | defer ca.close(); |
| 1371 | try proto.writeFrame(ca.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | 1348 | const cb = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 1372 | const cb = try std.net.connectUnixSocket(sock_path); | ||
| 1373 | defer cb.close(); | 1349 | defer cb.close(); |
| 1374 | try attachNamed(cb.handle, 80, 24, "b"); | ||
| 1375 | const fa = (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400)) orelse | 1350 | const fa = (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400)) orelse |
| 1376 | return error.NoSnapshotA; | 1351 | return error.NoSnapshotA; |
| 1377 | fa.deinit(alloc); | 1352 | fa.deinit(alloc); |
| @@ -1397,12 +1372,10 @@ test "Server: sessions_req answers every live name, whoever asks" { | |||
| 1397 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 1372 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 1398 | defer srv.deinit(); | 1373 | defer srv.deinit(); |
| 1399 | 1374 | ||
| 1400 | const ca = try std.net.connectUnixSocket(sock_path); | 1375 | const ca = try dial.dialAttach(sock_path, 80, 24); |
| 1401 | defer ca.close(); | 1376 | defer ca.close(); |
| 1402 | try proto.writeFrame(ca.handle, .attach, &proto.encodeAttach(80, 24, 0, 0)); | 1377 | const cb = try dial.dialAttachNamed(sock_path, 80, 24, "work"); |
| 1403 | const cb = try std.net.connectUnixSocket(sock_path); | ||
| 1404 | defer cb.close(); | 1378 | defer cb.close(); |
| 1405 | try attachNamed(cb.handle, 80, 24, "work"); | ||
| 1406 | const fa = (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400)) orelse | 1379 | const fa = (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400)) orelse |
| 1407 | return error.NoSnapshotA; | 1380 | return error.NoSnapshotA; |
| 1408 | fa.deinit(alloc); | 1381 | fa.deinit(alloc); |
| @@ -1430,12 +1403,10 @@ test "Server: an attached client's mismatched status tail is ignored" { | |||
| 1430 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); | 1403 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); |
| 1431 | defer srv.deinit(); | 1404 | defer srv.deinit(); |
| 1432 | 1405 | ||
| 1433 | const ca = try std.net.connectUnixSocket(sock_path); | 1406 | const ca = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 1434 | defer ca.close(); | 1407 | defer ca.close(); |
| 1435 | try attachNamed(ca.handle, 80, 24, "a"); | 1408 | const cb = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 1436 | const cb = try std.net.connectUnixSocket(sock_path); | ||
| 1437 | defer cb.close(); | 1409 | defer cb.close(); |
| 1438 | try attachNamed(cb.handle, 80, 24, "b"); | ||
| 1439 | const fa = (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400)) orelse | 1410 | const fa = (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400)) orelse |
| 1440 | return error.NoSnapshotA; | 1411 | return error.NoSnapshotA; |
| 1441 | fa.deinit(alloc); | 1412 | fa.deinit(alloc); |
| @@ -1473,9 +1444,8 @@ test "Server: a session-less slot's status_req resolves the tail like an observe | |||
| 1473 | defer srv.deinit(); | 1444 | defer srv.deinit(); |
| 1474 | 1445 | ||
| 1475 | // Stand up session "b" for real, over an ordinary attach. | 1446 | // Stand up session "b" for real, over an ordinary attach. |
| 1476 | const cb = try std.net.connectUnixSocket(sock_path); | 1447 | const cb = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 1477 | defer cb.close(); | 1448 | defer cb.close(); |
| 1478 | try attachNamed(cb.handle, 80, 24, "b"); | ||
| 1479 | const fb = (try awaitFrame(alloc, &srv, cb.handle, .snapshot, 400)) orelse | 1449 | const fb = (try awaitFrame(alloc, &srv, cb.handle, .snapshot, 400)) orelse |
| 1480 | return error.NoSnapshotB; | 1450 | return error.NoSnapshotB; |
| 1481 | fb.deinit(alloc); | 1451 | fb.deinit(alloc); |
| @@ -1559,22 +1529,19 @@ test "Server: end_req with another client attached is refused with the count; fo | |||
| 1559 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); | 1529 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); |
| 1560 | defer srv.deinit(); | 1530 | defer srv.deinit(); |
| 1561 | 1531 | ||
| 1562 | const a1 = try std.net.connectUnixSocket(sock_path); | 1532 | const a1 = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 1563 | defer a1.close(); | 1533 | defer a1.close(); |
| 1564 | try attachNamed(a1.handle, 80, 24, "a"); | ||
| 1565 | (try awaitFrame(alloc, &srv, a1.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); | 1534 | (try awaitFrame(alloc, &srv, a1.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); |
| 1566 | const a2 = try std.net.connectUnixSocket(sock_path); | 1535 | const a2 = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 1567 | defer a2.close(); | 1536 | defer a2.close(); |
| 1568 | try attachNamed(a2.handle, 80, 24, "a"); | ||
| 1569 | (try awaitFrame(alloc, &srv, a2.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); | 1537 | (try awaitFrame(alloc, &srv, a2.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); |
| 1570 | const b1 = try std.net.connectUnixSocket(sock_path); | 1538 | const b1 = try dial.dialAttachNamed(sock_path, 80, 24, "b"); |
| 1571 | defer b1.close(); | 1539 | defer b1.close(); |
| 1572 | try attachNamed(b1.handle, 80, 24, "b"); | ||
| 1573 | (try awaitFrame(alloc, &srv, b1.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); | 1540 | (try awaitFrame(alloc, &srv, b1.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); |
| 1574 | const pid_a = shellPidOf(&srv, "a"); | 1541 | const pid_a = shellPidOf(&srv, "a"); |
| 1575 | 1542 | ||
| 1576 | // an observer asks: every attached client is "other" | 1543 | // an observer asks: every attached client is "other" |
| 1577 | const obs = try std.net.connectUnixSocket(sock_path); | 1544 | const obs = try dial.dial(sock_path); |
| 1578 | defer obs.close(); | 1545 | defer obs.close(); |
| 1579 | var rq: [proto.end_req_max_len]u8 = undefined; | 1546 | var rq: [proto.end_req_max_len]u8 = undefined; |
| 1580 | try proto.writeFrame(obs.handle, .end_req, proto.encodeEndReq(&rq, false, "a")); | 1547 | try proto.writeFrame(obs.handle, .end_req, proto.encodeEndReq(&rq, false, "a")); |
| @@ -1619,9 +1586,8 @@ test "Server: end_req alone on a session ends it at once, and an unknown name is | |||
| 1619 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); | 1586 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); |
| 1620 | defer srv.deinit(); | 1587 | defer srv.deinit(); |
| 1621 | 1588 | ||
| 1622 | const c = try std.net.connectUnixSocket(sock_path); | 1589 | const c = try dial.dialAttachNamed(sock_path, 80, 24, "solo"); |
| 1623 | defer c.close(); | 1590 | defer c.close(); |
| 1624 | try attachNamed(c.handle, 80, 24, "solo"); | ||
| 1625 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); | 1591 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); |
| 1626 | 1592 | ||
| 1627 | const pid = shellPidOf(&srv, "solo"); | 1593 | const pid = shellPidOf(&srv, "solo"); |
| @@ -1637,7 +1603,7 @@ test "Server: end_req alone on a session ends it at once, and an unknown name is | |||
| 1637 | } | 1603 | } |
| 1638 | try std.testing.expect(!alive(pid)); | 1604 | try std.testing.expect(!alive(pid)); |
| 1639 | 1605 | ||
| 1640 | const obs = try std.net.connectUnixSocket(sock_path); | 1606 | const obs = try dial.dial(sock_path); |
| 1641 | defer obs.close(); | 1607 | defer obs.close(); |
| 1642 | try proto.writeFrame(obs.handle, .end_req, proto.encodeEndReq(&rq, true, "nosuch")); | 1608 | try proto.writeFrame(obs.handle, .end_req, proto.encodeEndReq(&rq, true, "nosuch")); |
| 1643 | const r2 = (try awaitFrame(alloc, &srv, obs.handle, .end_reply, 200)) orelse return error.NoEndReply; | 1609 | const r2 = (try awaitFrame(alloc, &srv, obs.handle, .end_reply, 200)) orelse return error.NoEndReply; |
| @@ -1658,16 +1624,14 @@ test "Server: an observer's sessions_req is answered with every live name" { | |||
| 1658 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); | 1624 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); |
| 1659 | defer srv.deinit(); | 1625 | defer srv.deinit(); |
| 1660 | 1626 | ||
| 1661 | const cx = try std.net.connectUnixSocket(sock_path); | 1627 | const cx = try dial.dialAttachNamed(sock_path, 80, 24, "x"); |
| 1662 | defer cx.close(); | 1628 | defer cx.close(); |
| 1663 | try attachNamed(cx.handle, 80, 24, "x"); | ||
| 1664 | (try awaitFrame(alloc, &srv, cx.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); | 1629 | (try awaitFrame(alloc, &srv, cx.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); |
| 1665 | const cy = try std.net.connectUnixSocket(sock_path); | 1630 | const cy = try dial.dialAttachNamed(sock_path, 80, 24, "y"); |
| 1666 | defer cy.close(); | 1631 | defer cy.close(); |
| 1667 | try attachNamed(cy.handle, 80, 24, "y"); | ||
| 1668 | (try awaitFrame(alloc, &srv, cy.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); | 1632 | (try awaitFrame(alloc, &srv, cy.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); |
| 1669 | 1633 | ||
| 1670 | const obs = try std.net.connectUnixSocket(sock_path); | 1634 | const obs = try dial.dial(sock_path); |
| 1671 | defer obs.close(); | 1635 | defer obs.close(); |
| 1672 | try proto.writeFrame(obs.handle, .sessions_req, ""); | 1636 | try proto.writeFrame(obs.handle, .sessions_req, ""); |
| 1673 | const r = (try awaitFrame(alloc, &srv, obs.handle, .sessions_reply, 200)) orelse return error.NoSessionsReply; | 1637 | const r = (try awaitFrame(alloc, &srv, obs.handle, .sessions_reply, 200)) orelse return error.NoSessionsReply; |
| @@ -1709,9 +1673,8 @@ test "Server: an accepted end kills a shell that ignores TERM and HUP" { | |||
| 1709 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); | 1673 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); |
| 1710 | defer srv.deinit(); | 1674 | defer srv.deinit(); |
| 1711 | 1675 | ||
| 1712 | const c = try std.net.connectUnixSocket(sock_path); | 1676 | const c = try dial.dialAttachNamed(sock_path, 80, 24, "stubborn"); |
| 1713 | defer c.close(); | 1677 | defer c.close(); |
| 1714 | try attachNamed(c.handle, 80, 24, "stubborn"); | ||
| 1715 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); | 1678 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); |
| 1716 | 1679 | ||
| 1717 | // The shell ignores both signals `requestExit` has, and never reads stdin | 1680 | // The shell ignores both signals `requestExit` has, and never reads stdin |
| @@ -1749,9 +1712,8 @@ test "Server: a keystroke into an ending session does not cost that client its e | |||
| 1749 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 1712 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 1750 | defer srv.deinit(); | 1713 | defer srv.deinit(); |
| 1751 | 1714 | ||
| 1752 | const c = try std.net.connectUnixSocket(sock_path); | 1715 | const c = try dial.dialAttachNamed(sock_path, 80, 24, "typing"); |
| 1753 | defer c.close(); | 1716 | defer c.close(); |
| 1754 | try attachNamed(c.handle, 80, 24, "typing"); | ||
| 1755 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); | 1717 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); |
| 1756 | 1718 | ||
| 1757 | var rq: [proto.end_req_max_len]u8 = undefined; | 1719 | var rq: [proto.end_req_max_len]u8 = undefined; |
| @@ -1781,9 +1743,8 @@ test "Server: a repeated end_req does not push the SIGKILL deadline out" { | |||
| 1781 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); | 1743 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = script }); |
| 1782 | defer srv.deinit(); | 1744 | defer srv.deinit(); |
| 1783 | 1745 | ||
| 1784 | const c = try std.net.connectUnixSocket(sock_path); | 1746 | const c = try dial.dialAttachNamed(sock_path, 80, 24, "nag"); |
| 1785 | defer c.close(); | 1747 | defer c.close(); |
| 1786 | try attachNamed(c.handle, 80, 24, "nag"); | ||
| 1787 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); | 1748 | (try awaitFrame(alloc, &srv, c.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); |
| 1788 | 1749 | ||
| 1789 | const pid = shellPidOf(&srv, "nag"); | 1750 | const pid = shellPidOf(&srv, "nag"); |
| @@ -1795,7 +1756,7 @@ test "Server: a repeated end_req does not push the SIGKILL deadline out" { | |||
| 1795 | 1756 | ||
| 1796 | // An observer nagging faster than the grace: if each accept restarted the | 1757 | // An observer nagging faster than the grace: if each accept restarted the |
| 1797 | // clock, the shell would outlive every deadline it was ever given. | 1758 | // clock, the shell would outlive every deadline it was ever given. |
| 1798 | const obs = try std.net.connectUnixSocket(sock_path); | 1759 | const obs = try dial.dial(sock_path); |
| 1799 | defer obs.close(); | 1760 | defer obs.close(); |
| 1800 | var waited: u32 = 0; | 1761 | var waited: u32 = 0; |
| 1801 | while (alive(pid) and waited < 3000) : (waited += 80) { | 1762 | while (alive(pid) and waited < 3000) : (waited += 80) { |
src/server/server_test_upgrade.zig
| Old | New | ||
|---|---|---|---|
| @@ -3,6 +3,7 @@ const proto = @import("term").protocol; | |||
| 3 | const upgrade = @import("upgrade.zig"); | 3 | const upgrade = @import("upgrade.zig"); |
| 4 | const TmpDir = @import("testtmp").TmpDir; | 4 | const TmpDir = @import("testtmp").TmpDir; |
| 5 | const h = @import("server_test_harness.zig"); | 5 | const h = @import("server_test_harness.zig"); |
| 6 | const dial = h.dial; | ||
| 6 | const srv_mod = @import("server.zig"); | 7 | const srv_mod = @import("server.zig"); |
| 7 | const SessionTable = @import("server_sessions.zig").SessionTable; | 8 | const SessionTable = @import("server_sessions.zig").SessionTable; |
| 8 | const Server = srv_mod.Server; | 9 | const Server = srv_mod.Server; |
| @@ -162,7 +163,7 @@ test "initFromManifest: an adopted session answers a status_req without having b | |||
| 162 | 163 | ||
| 163 | // The socket the OLD daemon bound still answers, over the inherited | 164 | // The socket the OLD daemon bound still answers, over the inherited |
| 164 | // listener fd: no second bind, no claim, no unlink in between. | 165 | // listener fd: no second bind, no claim, no unlink in between. |
| 165 | const obs = try std.net.connectUnixSocket(sock_path); | 166 | const obs = try dial.dial(sock_path); |
| 166 | defer obs.close(); | 167 | defer obs.close(); |
| 167 | try proto.writeFrame(obs.handle, .status_req, ""); | 168 | try proto.writeFrame(obs.handle, .status_req, ""); |
| 168 | const reply = (try awaitFrame(alloc, &srv2, obs.handle, .status_reply, 400)) orelse | 169 | const reply = (try awaitFrame(alloc, &srv2, obs.handle, .status_reply, 400)) orelse |
| @@ -405,7 +406,7 @@ test "Server: an upgrade asked for during a session's hangup is refused, not att | |||
| 405 | 406 | ||
| 406 | // The shell outlives the hangup by the whole grace, so the exec's | 407 | // The shell outlives the hangup by the whole grace, so the exec's |
| 407 | // clearCloexec and the manifest's pty_fd would both see -1. | 408 | // clearCloexec and the manifest's pty_fd would both see -1. |
| 408 | const ender = try std.net.connectUnixSocket(sock_path); | 409 | const ender = try dial.dial(sock_path); |
| 409 | defer ender.close(); | 410 | defer ender.close(); |
| 410 | var rq: [proto.end_req_max_len]u8 = undefined; | 411 | var rq: [proto.end_req_max_len]u8 = undefined; |
| 411 | try proto.writeFrame(ender.handle, .end_req, proto.encodeEndReq(&rq, true, "")); | 412 | try proto.writeFrame(ender.handle, .end_req, proto.encodeEndReq(&rq, true, "")); |
| @@ -431,7 +432,7 @@ test "Server: an upgrade asked for during a session's hangup is refused, not att | |||
| 431 | const cand = try std.fmt.allocPrint(alloc, "{s}/fakemux.sh", .{tmp.path()}); | 432 | const cand = try std.fmt.allocPrint(alloc, "{s}/fakemux.sh", .{tmp.path()}); |
| 432 | defer alloc.free(cand); | 433 | defer alloc.free(cand); |
| 433 | 434 | ||
| 434 | const obs = try std.net.connectUnixSocket(sock_path); | 435 | const obs = try dial.dial(sock_path); |
| 435 | defer obs.close(); | 436 | defer obs.close(); |
| 436 | var ureq: [512]u8 = undefined; | 437 | var ureq: [512]u8 = undefined; |
| 437 | try proto.writeFrame(obs.handle, .upgrade_req, try proto.encodeUpgradeReq(&ureq, .{ | 438 | try proto.writeFrame(obs.handle, .upgrade_req, try proto.encodeUpgradeReq(&ureq, .{ |
| @@ -532,7 +533,7 @@ test "Server: an EMPTY daemon upgrades — the manifest names no session and the | |||
| 532 | 533 | ||
| 533 | // Serving, on the listener fd the old image bound: an empty daemon that | 534 | // Serving, on the listener fd the old image bound: an empty daemon that |
| 534 | // came through an exec is still the box a client can be born on. | 535 | // came through an exec is still the box a client can be born on. |
| 535 | const obs = try std.net.connectUnixSocket(sock_path); | 536 | const obs = try dial.dial(sock_path); |
| 536 | defer obs.close(); | 537 | defer obs.close(); |
| 537 | try proto.writeFrame(obs.handle, .stats_req, ""); | 538 | try proto.writeFrame(obs.handle, .stats_req, ""); |
| 538 | const reply = (try awaitFrame(alloc, &srv2, obs.handle, .stats_reply, 400)) orelse | 539 | const reply = (try awaitFrame(alloc, &srv2, obs.handle, .stats_reply, 400)) orelse |
| @@ -557,9 +558,8 @@ test "Server: an accepted end cancels a pending upgrade — the exec never sees | |||
| 557 | // Plural: the end takes ONE session and the upgrade would have carried | 558 | // Plural: the end takes ONE session and the upgrade would have carried |
| 558 | // the others, so a guard that only looked at the ending slot's own | 559 | // the others, so a guard that only looked at the ending slot's own |
| 559 | // clients would still pass here. | 560 | // clients would still pass here. |
| 560 | const ca = try std.net.connectUnixSocket(sock_path); | 561 | const ca = try dial.dialAttachNamed(sock_path, 80, 24, "a"); |
| 561 | defer ca.close(); | 562 | defer ca.close(); |
| 562 | try h.attachNamed(ca.handle, 80, 24, "a"); | ||
| 563 | (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); | 563 | (try awaitFrame(alloc, &srv, ca.handle, .snapshot, 400) orelse return error.NoState).deinit(alloc); |
| 564 | 564 | ||
| 565 | try tmp.dir.writeFile(.{ | 565 | try tmp.dir.writeFile(.{ |
| @@ -580,10 +580,10 @@ test "Server: an accepted end cancels a pending upgrade — the exec never sees | |||
| 580 | // Two observers, seated in slot order BEFORE either speaks: the hazard | 580 | // Two observers, seated in slot order BEFORE either speaks: the hazard |
| 581 | // is one pump servicing an upgrade at a lower slot and an end at a | 581 | // is one pump servicing an upgrade at a lower slot and an end at a |
| 582 | // higher one, after which `run` execs on a master that is already -1. | 582 | // higher one, after which `run` execs on a master that is already -1. |
| 583 | const up = try std.net.connectUnixSocket(sock_path); | 583 | const up = try dial.dial(sock_path); |
| 584 | defer up.close(); | 584 | defer up.close(); |
| 585 | try srv.pumpOnce(20); | 585 | try srv.pumpOnce(20); |
| 586 | const ender = try std.net.connectUnixSocket(sock_path); | 586 | const ender = try dial.dial(sock_path); |
| 587 | defer ender.close(); | 587 | defer ender.close(); |
| 588 | try srv.pumpOnce(20); | 588 | try srv.pumpOnce(20); |
| 589 | 589 | ||