diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-09 10:41:33 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-10 09:17:07 -0300 |
| commit | 9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a (patch) | |
| tree | 91fce278abc85e4f7491de8b5f02184c8a2d5a56 /test/e2e_harness.zig | |
| parent | 2b1fac6fecbf7d7cc08f9dad24687b05e9968c9a (diff) | |
| download | pardes-9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a.tar.gz pardes-9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a.zip | |
replace ArrayLists with bounded storage
Diffstat (limited to 'test/e2e_harness.zig')
| -rw-r--r-- | test/e2e_harness.zig | 50 |
1 files changed, 40 insertions, 10 deletions
diff --git a/test/e2e_harness.zig b/test/e2e_harness.zig index eb4c7a2f..c188827e 100644 --- a/test/e2e_harness.zig +++ b/test/e2e_harness.zig @@ -22,6 +22,24 @@ pub fn nowMs() i64 { _ = libc.clock_gettime(.MONOTONIC, &ts); return @as(i64, @intCast(ts.sec)) * 1000 + @divFloor(@as(i64, @intCast(ts.nsec)), 1_000_000); } +const raw_capture_max = 64 * 1024 * 1024; + +pub const RawCapture = struct { + bytes: [raw_capture_max]u8 = undefined, + items: []u8 = &.{}, + full: bool = false, + + fn appendSlice(self: *RawCapture, chunk: []const u8) error{CaptureFull}!void { + if (self.full) return error.CaptureFull; + if (chunk.len > self.bytes.len - self.items.len) { + self.full = true; + return error.CaptureFull; + } + const end = self.items.len + chunk.len; + @memcpy(self.bytes[self.items.len..end], chunk); + self.items = self.bytes[0..end]; + } +}; /// E2E test harness: forks the app in a pty and parses its output with a /// ghostty terminal emulator. Tests read the 2D screen GRID (via plainString), @@ -47,8 +65,9 @@ pub const Harness = struct { trace: bool = false, /// every raw byte the app has emitted (accumulated in pump). Lets tests assert /// on control sequences the emulator consumes and never renders (e.g. OSC 52 - /// clipboard writes). gpa-owned; freed in deinit. - raw: std.ArrayList(u8) = .empty, + /// clipboard writes). Capture is bounded explicitly so a runaway child cannot + /// consume unbounded test-runner memory. + raw: *RawCapture, pub fn init(gpa: std.mem.Allocator, exe: [*:0]const u8, rows: u16, cols: u16) !Harness { return initArgs(gpa, exe, rows, cols, null); @@ -63,14 +82,16 @@ pub const Harness = struct { /// Like init, but passes extra CLI args (e.g. "-l", "dump.zon"). pub fn initArgv(gpa: std.mem.Allocator, exe: [*:0]const u8, rows: u16, cols: u16, args: []const ?[*:0]const u8) !Harness { + const argv = try gpa.allocSentinel(?[*:0]const u8, args.len + 1, null); + defer gpa.free(argv); + argv[0] = exe; + for (args, 0..) |a, i| argv[i + 1] = a; + var master: c_int = undefined; const ws = posix.winsize{ .row = rows, .col = cols, .xpixel = 0, .ypixel = 0 }; const pid = forkpty(&master, null, null, &ws); if (pid == 0) { - var argv: [8:null]?[*:0]const u8 = @splat(null); - argv[0] = exe; - for (args, 0..) |a, i| argv[i + 1] = a; - _ = execvp(exe, &argv); + _ = execvp(exe, argv.ptr); _exit(127); } const term = try gpa.create(ghostty_vt.Terminal); @@ -84,6 +105,9 @@ pub const Harness = struct { // vtStream() captures a pointer to `term`; term is heap-allocated so the // pointer stays valid for the life of the harness. const stream = term.vtStream(); + const raw = try gpa.create(RawCapture); + errdefer gpa.destroy(raw); + raw.* = .{}; return .{ .gpa = gpa, .master = master, @@ -92,6 +116,7 @@ pub const Harness = struct { .stream = stream, .rows = rows, .cols = cols, + .raw = raw, }; } @@ -99,7 +124,7 @@ pub const Harness = struct { posix.kill(self.pid, posix.SIG.KILL) catch {}; _ = libc.close(self.master); self.stream.deinit(); - self.raw.deinit(self.gpa); + self.gpa.destroy(self.raw); self.term.deinit(self.gpa); self.gpa.destroy(self.term); } @@ -128,7 +153,7 @@ pub const Harness = struct { if ((fds[0].revents & posix.POLL.IN) != 0) { const n = posix.read(self.master, &buf) catch break; if (n == 0) break; - self.raw.appendSlice(self.gpa, buf[0..n]) catch {}; + self.recordRaw(buf[0..n]); self.stream.nextSlice(buf[0..n]); } } @@ -147,7 +172,7 @@ pub const Harness = struct { var buf: [4096]u8 = undefined; const n = posix.read(self.master, &buf) catch return false; if (n == 0) return false; - self.raw.appendSlice(self.gpa, buf[0..n]) catch {}; + self.recordRaw(buf[0..n]); self.stream.nextSlice(buf[0..n]); return true; } @@ -206,7 +231,7 @@ pub const Harness = struct { if ((fds[0].revents & posix.POLL.IN) != 0) { const n = posix.read(self.master, &buf) catch break; if (n == 0) break; - self.raw.appendSlice(self.gpa, buf[0..n]) catch {}; + self.recordRaw(buf[0..n]); self.stream.nextSlice(buf[0..n]); } const text = try self.screenText(); @@ -219,6 +244,11 @@ pub const Harness = struct { if (self.trace) self.traceScreen("waitFor miss"); return false; } + fn recordRaw(self: *Harness, bytes: []const u8) void { + self.raw.appendSlice(bytes) catch |err| switch (err) { + error.CaptureFull => {}, + }; + } /// The visual cursor position (viewport coords): { x, y }. pub fn cursor(self: *Harness) struct { x: u16, y: u16 } { |
