diff options
| author | Gabriel Schneider <[email protected]> | 2026-07-06 18:00:54 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-01 15:02:07 -0300 |
| commit | 7a83d1548493b043e2e77022c1c87d348377ac7b (patch) | |
| tree | 75bcac77800b13695a953a3398b5bb8a95e017dd /test | |
| parent | de4def3548a6729b0dfd2120495a61beef8c8c2c (diff) | |
| download | pardes-7a83d1548493b043e2e77022c1c87d348377ac7b.tar.gz pardes-7a83d1548493b043e2e77022c1c87d348377ac7b.zip | |
posix portability: linux syscalls -> libc (std.c) in one shared path. All raw std.os.linux.* call sites converted with the errno-convention flip (libc returns -1 + thread-local errno, not -errno): open/close/read/write/rename in tty.zig, gui.zig, look.zig; harness too (snapshot.zig: getcwd/mkdir/chdir/fork+waitpid/getdents64->opendir-readdir, e2e_harness.zig: clock_gettime/write/close), so the parity suite itself is POSIX-portable. Per-OS branches kept only where platforms genuinely diverge: shellCwd in look.zig (linux /proc/<pid>/cwd readlink; darwin libproc proc_pidinfo PROC_PIDVNODEPATHINFO with a hand-declared extern; other POSIX degrades to null), bash path (/usr/bin/bash on linux, /bin/bash elsewhere), TIOCSWINSZ constant (absent from std.c.T on darwin). forkpty/execv/chdir externs were already libc and are portable (fork-safety preserved: absolute-path execv, no malloc between fork and exec, initial spawns before worker threads). build.zig: forward target/optimize to the ghostty dep for native/cross (web keeps its no-target fetch), disable ghostty simd for darwin cross-builds (apple_sdk needs xcrun; scalar fallback = web config), link libutil on the BSDs. Pinned ghostty needs two one-line zig-0.16 darwin fixes, applied in the zig-pkg cache and documented in build.zig. Verified: aarch64-macos + x86_64-macos + x86_64-freebsd cross-compiles green; linux native tty + gui + web green; snapshot suite 18/18 with goldens untouched. Out of scope: gui-on-macos needs Metal shaders (SDL GPU rejects our SPIR-V there).
Diffstat (limited to 'test')
| -rw-r--r-- | test/e2e_harness.zig | 37 | ||||
| -rw-r--r-- | test/snapshot.zig | 63 |
2 files changed, 41 insertions, 59 deletions
diff --git a/test/e2e_harness.zig b/test/e2e_harness.zig index 6354aa4b..9099f13e 100644 --- a/test/e2e_harness.zig +++ b/test/e2e_harness.zig @@ -1,8 +1,11 @@ const std = @import("std"); const posix = std.posix; -const linux = std.os.linux; +const libc = std.c; const ghostty_vt = @import("ghostty-vt"); +// TIOCSWINSZ: absent from std.c.T on darwin — _IOW('t', 103, winsize) +const TIOCSWINSZ: c_int = @bitCast(@as(u32, if (@hasDecl(posix.T, "IOCSWINSZ")) posix.T.IOCSWINSZ else 0x80087467)); + extern "c" fn forkpty( amaster: *c_int, name: ?[*:0]u8, @@ -13,9 +16,9 @@ extern "c" fn execvp(file: [*:0]const u8, argv: [*:null]const ?[*:0]const u8) c_ extern "c" fn _exit(status: c_int) noreturn; fn nowMs() i64 { - var ts: linux.timespec = undefined; - _ = linux.clock_gettime(linux.CLOCK.MONOTONIC, &ts); - return ts.sec * 1000 + @divFloor(@as(i64, ts.nsec), 1_000_000); + var ts: libc.timespec = undefined; + _ = libc.clock_gettime(.MONOTONIC, &ts); + return @as(i64, @intCast(ts.sec)) * 1000 + @divFloor(@as(i64, @intCast(ts.nsec)), 1_000_000); } /// E2E test harness: forks the app in a pty and parses its output with a @@ -92,7 +95,7 @@ pub const Harness = struct { pub fn deinit(self: *Harness) void { posix.kill(self.pid, posix.SIG.KILL) catch {}; - _ = linux.close(self.master); + _ = libc.close(self.master); self.stream.deinit(); self.raw.deinit(self.gpa); self.term.deinit(self.gpa); @@ -103,10 +106,9 @@ pub const Harness = struct { pub fn send(self: *Harness, bytes: []const u8) !void { var off: usize = 0; while (off < bytes.len) { - const rc = linux.write(self.master, bytes[off..].ptr, bytes.len - off); - const n: isize = @bitCast(rc); + const n = libc.write(self.master, bytes[off..].ptr, bytes.len - off); if (n < 0) { - if (n == -@as(isize, @intFromEnum(linux.E.INTR))) continue; + if (libc.errno(n) == .INTR) continue; return error.WriteFailed; } off += @intCast(n); @@ -139,7 +141,7 @@ pub const Harness = struct { self.cols = cols; self.term.resize(self.gpa, cols, rows) catch {}; const ws = posix.winsize{ .row = rows, .col = cols, .xpixel = 0, .ypixel = 0 }; - _ = posix.system.ioctl(self.master, posix.T.IOCSWINSZ, @intFromPtr(&ws)); + _ = posix.system.ioctl(self.master, TIOCSWINSZ, @intFromPtr(&ws)); } /// The full viewport text (plainString), newline-joined rows. Caller frees. @@ -261,18 +263,15 @@ pub const Harness = struct { } }; -/// Write a file (helper for test setup, raw linux syscalls). +/// Write a file (helper for test setup, raw libc). pub fn writeFile(path: [*:0]const u8, data: []const u8) !void { - const rc = linux.open(path, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, 0o644); - const sfd: isize = @bitCast(rc); - if (sfd < 0) return error.OpenFailed; - const fd: c_int = @intCast(sfd); - defer _ = linux.close(fd); + const fd = libc.open(path, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, @as(libc.mode_t, 0o644)); + if (fd < 0) return error.OpenFailed; + defer _ = libc.close(fd); var off: usize = 0; while (off < data.len) { - const w = linux.write(fd, data[off..].ptr, data.len - off); - const sw: isize = @bitCast(w); - if (sw <= 0) break; - off += @intCast(sw); + const w = libc.write(fd, data[off..].ptr, data.len - off); + if (w <= 0) break; + off += @intCast(w); } } diff --git a/test/snapshot.zig b/test/snapshot.zig index c1bf03c0..10aa0c86 100644 --- a/test/snapshot.zig +++ b/test/snapshot.zig @@ -28,8 +28,7 @@ // snap <label> capture grid text + cursor // snapstyle <label> capture per-cell style runs (fg,bg,attrs) const std = @import("std"); -const posix = std.posix; -const linux = std.os.linux; +const libc = std.c; const ghostty_vt = @import("ghostty-vt"); const eh = @import("e2e_harness.zig"); @@ -48,9 +47,8 @@ pub fn main(init: std.process.Init) !void { // argv: exe-under-test, then flags/scripts. All paths made absolute up // front because runScript chdirs into a per-script workdir. var cwd_buf: [4096]u8 = undefined; - const cwd_rc: isize = @bitCast(linux.getcwd(&cwd_buf, cwd_buf.len)); - if (cwd_rc <= 0) fatal("getcwd failed", .{}); - const orig_cwd = cwd_buf[0..@intCast(cwd_rc - 1)]; + const cwd_p = libc.getcwd(&cwd_buf, cwd_buf.len) orelse fatal("getcwd failed", .{}); + const orig_cwd = std.mem.span(@as([*:0]u8, @ptrCast(cwd_p))); var exe: ?[]const u8 = null; var update = false; @@ -461,41 +459,26 @@ fn absPath(arena: std.mem.Allocator, cwd: []const u8, p: []const u8) ![]const u8 fn listSnaps(arena: std.mem.Allocator, dir_path: []const u8, scripts: *std.ArrayList([]const u8)) !void { const dir_z = try arena.dupeZ(u8, dir_path); - const rc = linux.open(dir_z, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, 0); - const sfd: isize = @bitCast(rc); - if (sfd < 0) fatal("no snapshots/ dir at {s}", .{dir_path}); - const fd: i32 = @intCast(sfd); - defer _ = linux.close(fd); - var buf: [8192]u8 align(@alignOf(linux.dirent64)) = undefined; - while (true) { - const r = linux.getdents64(fd, &buf, buf.len); - const n: isize = @bitCast(r); - if (n <= 0) break; - var off: usize = 0; - while (off < @as(usize, @intCast(n))) { - const d: *align(1) linux.dirent64 = @ptrCast(&buf[off]); - const name = std.mem.span(@as([*:0]const u8, @ptrCast(&d.name))); - if (std.mem.endsWith(u8, name, ".snap")) - try scripts.append(arena, try std.fmt.allocPrint(arena, "{s}/{s}", .{ dir_path, name })); - off += d.reclen; - } + const dir = libc.opendir(dir_z) orelse fatal("no snapshots/ dir at {s}", .{dir_path}); + defer _ = libc.closedir(dir); + while (libc.readdir(dir)) |d| { + const name = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&d.name)), 0); + if (std.mem.endsWith(u8, name, ".snap")) + try scripts.append(arena, try std.fmt.allocPrint(arena, "{s}/{s}", .{ dir_path, name })); } } fn readFileAlloc(arena: std.mem.Allocator, path: []const u8) ![]u8 { const path_z = try arena.dupeZ(u8, path); - const rc = linux.open(path_z, .{ .ACCMODE = .RDONLY }, 0); - const sfd: isize = @bitCast(rc); - if (sfd < 0) return error.OpenFailed; - const fd: i32 = @intCast(sfd); - defer _ = linux.close(fd); + const fd = libc.open(path_z, .{ .ACCMODE = .RDONLY }); + if (fd < 0) return error.OpenFailed; + defer _ = libc.close(fd); var buf: std.ArrayList(u8) = .empty; var chunk: [16384]u8 = undefined; while (true) { - const r = linux.read(fd, &chunk, chunk.len); - const n: isize = @bitCast(r); + const n = libc.read(fd, &chunk, chunk.len); if (n < 0) { - if (n == -@as(isize, @intFromEnum(linux.E.INTR))) continue; + if (libc.errno(n) == .INTR) continue; return error.ReadFailed; } if (n == 0) break; @@ -506,15 +489,15 @@ fn readFileAlloc(arena: std.mem.Allocator, path: []const u8) ![]u8 { fn mkdir(arena: std.mem.Allocator, path: []const u8, exist_ok: bool) !void { const path_z = try arena.dupeZ(u8, path); - const n: isize = @bitCast(linux.mkdir(path_z, 0o755)); - if (n < 0) { - if (exist_ok and n == -@as(isize, @intFromEnum(linux.E.EXIST))) return; + const rc = libc.mkdir(path_z, 0o755); + if (rc != 0) { + if (exist_ok and libc.errno(rc) == .EXIST) return; return error.MkdirFailed; } } -fn chdirRc(path_z: [:0]const u8) isize { - return @bitCast(linux.chdir(path_z.ptr)); +fn chdirRc(path_z: [:0]const u8) c_int { + return libc.chdir(path_z.ptr); } fn rmrf(path: []const u8) void { @@ -522,15 +505,15 @@ fn rmrf(path: []const u8) void { if (path.len >= path_buf.len) return; @memcpy(path_buf[0..path.len], path); path_buf[path.len] = 0; - const pid: isize = @bitCast(linux.fork()); + const pid = libc.fork(); if (pid == 0) { const argv: [4:null]?[*:0]const u8 = .{ "rm", "-rf", @as([*:0]const u8, @ptrCast(&path_buf)), null }; _ = execvp("rm", &argv); - linux.exit_group(127); + libc._exit(127); } if (pid < 0) return; - var status: u32 = 0; - _ = linux.wait4(@intCast(pid), &status, 0, null); + var status: c_int = 0; + _ = libc.waitpid(pid, &status, 0); } fn printFirstDiff(golden: []const u8, actual: []const u8) void { |
