a73x

7367068b

feat(pty): add read/write helpers

a73x   2026-04-08 06:02


diff --git a/src/pty.zig b/src/pty.zig
index 01e6e9b..2185a54 100644
--- a/src/pty.zig
+++ b/src/pty.zig
@@ -52,6 +52,14 @@ pub const Pty = struct {
        };
    }

    pub fn read(self: *Pty, buf: []u8) !usize {
        return std.posix.read(self.master_fd, buf);
    }

    pub fn write(self: *Pty, data: []const u8) !usize {
        return std.posix.write(self.master_fd, data);
    }

    pub fn deinit(self: *Pty) void {
        std.posix.close(self.master_fd);
        _ = std.c.kill(self.child_pid, std.c.SIG.TERM);
@@ -59,6 +67,41 @@ pub const Pty = struct {
    }
};

test "Pty.write and read echoes through shell" {
    var pty = try Pty.spawn(.{
        .cols = 80,
        .rows = 24,
        .shell = "/bin/sh",
    });
    defer pty.deinit();

    // Give shell a moment to start
    std.Thread.sleep(100 * std.time.ns_per_ms);

    // Write a command
    _ = try pty.write("echo hello\n");

    // Drain output for up to 1 second
    var buf: [4096]u8 = undefined;
    var seen_hello = false;
    const deadline = std.time.nanoTimestamp() + 1 * std.time.ns_per_s;
    while (std.time.nanoTimestamp() < deadline) {
        const n = pty.read(&buf) catch |err| switch (err) {
            error.WouldBlock => {
                std.Thread.sleep(10 * std.time.ns_per_ms);
                continue;
            },
            else => return err,
        };
        if (n == 0) break;
        if (std.mem.indexOf(u8, buf[0..n], "hello") != null) {
            seen_hello = true;
            break;
        }
    }
    try std.testing.expect(seen_hello);
}

test "Pty.spawn launches /bin/sh and returns valid fd" {
    var pty = try Pty.spawn(.{
        .cols = 80,