Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions app/control/Server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1275,18 +1275,21 @@ test "a stopped server ends a keep-alive connection waiting for its next request
}

/// A client that asks for `GET /remote` to become a byte stream: reads the `101`, writes `ping`
/// right behind its request, reads four bytes, then reads to the end unless `leave`, which closes
/// the socket as soon as it has the head.
/// right behind its request, then holds the socket open after four reply bytes until released.
/// With `leave`, closes the socket as soon as it has the head.
const UpgradePeer = struct {
io: Io,
path: []const u8,
leave: bool = false,
head: [160]u8 = undefined,
head_len: usize = 0,
reply: [4]u8 = undefined,
ended: bool = false,
reply_complete: bool = false,
replied: Io.Event = .unset,
release: Io.Event = .unset,

fn run(self: *UpgradePeer) void {
defer self.replied.set(self.io);
const addr = Io.net.UnixAddress.init(self.path) catch return;
const stream = addr.connect(self.io) catch return;
defer stream.close(self.io);
Expand All @@ -1304,8 +1307,9 @@ const UpgradePeer = struct {
}
if (self.leave) return;
r.interface.readSliceAll(&self.reply) catch return;
var more: [1]u8 = undefined;
self.ended = if (r.interface.readSliceShort(&more)) |n| n == 0 else |_| true;
self.reply_complete = true;
self.replied.set(self.io);
self.release.wait(self.io) catch return;
}
};

Expand All @@ -1331,18 +1335,25 @@ test "an upgraded connection carries bytes both ways until the app closes it" {
const path = try testSocket(gpa, &tmp);
defer gpa.free(path);
defer removeTestSocket(path);
var link: Link = .init(gpa, io, noWake);
defer link.deinit();
var server: Server = .{ .gpa = gpa, .io = io, .socket_path = path, .wake = noWake };
try server.start();
defer server.stop();

var peer: UpgradePeer = .{ .io = io, .path = path };
const thread = try std.Thread.spawn(.{}, UpgradePeer.run, .{&peer});
const thread = std.Thread.spawn(.{}, UpgradePeer.run, .{&peer}) catch |err| {
server.stop();
return err;
};
defer {
server.stop();
peer.release.set(io);
thread.join();
}
var taken: std.ArrayList(*Request) = .empty;
defer taken.deinit(gpa);
const req = try takeOne(&server, &taken);
try std.testing.expectEqualStrings("fizzy-remote-view/1", req.upgrade.?);
var link: Link = .init(gpa, io, noWake);
defer link.deinit();
req.answerUpgrade(&server, &link);

// What the client wrote behind its request is the link's.
Expand All @@ -1355,13 +1366,14 @@ test "an upgraded connection carries bytes both ways until the app closes it" {
}
try std.testing.expectEqualStrings("ping", got.items);
try link.send("pong");
// Closing ends the client's read, after what was queued is written.
// The link finishes while the peer still holds its socket open: app close ends it.
// Windows AF_UNIX does not reliably deliver EOF to the peer.
link.close();
thread.join();
try peer.replied.wait(io);
try std.testing.expect(peer.reply_complete);
try std.testing.expect(std.mem.startsWith(u8, peer.head[0..peer.head_len], "HTTP/1.1 101 Switching Protocols\r\n"));
try std.testing.expect(std.mem.find(u8, peer.head[0..peer.head_len], "upgrade: fizzy-remote-view/1\r\n") != null);
try std.testing.expectEqualStrings("pong", &peer.reply);
try std.testing.expect(peer.ended);
waited = 0;
while (!link.isGone() and waited < 2000) : (waited += 1) io.sleep(.fromMilliseconds(1), .awake) catch {};
try std.testing.expect(link.isGone());
Expand Down
5 changes: 3 additions & 2 deletions sdk/tape/Script.zig
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,9 @@ test "a lacked name is left out of the script and an offered name stays" {
var s: Script = .init(testing.allocator, "t", "T");
try s.keyframe(.{ .root = "demo://t" });
try testing.expect(!s.lacksName("missing"));
s.lacks = &.{"missing"};
for ([_][]const u8{ "missing", "offered" }) |name| {
s.lacks = &.{ "missing", "also_missing" };
try testing.expect(!s.lacksName("miss"));
for ([_][]const u8{ "missing", "offered", "also_missing" }) |name| {
if (s.lacksName(name)) continue;
try s.command(name);
}
Expand Down
5 changes: 4 additions & 1 deletion src/editor/demos/tour.zig
Original file line number Diff line number Diff line change
Expand Up @@ -219,5 +219,8 @@ test "a frost-first tour names the frosted glass it shows" {
const caption = for (tape.captions) |c| {
if (c.at >= chapter.at) break c;
} else return error.TestUnexpectedResult;
try std.testing.expect(std.mem.find(u8, caption.text, "one material, frosted glass here…") != null);
try std.testing.expectEqualStrings(
"Over the code you can see what it is made of. The window, its floats, menus and dialogs are one material, frosted glass here…",
caption.text,
);
}
Loading