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/hxdiff.zig | |
| parent | 2b1fac6fecbf7d7cc08f9dad24687b05e9968c9a (diff) | |
| download | pardes-9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a.tar.gz pardes-9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a.zip | |
replace ArrayLists with bounded storage
Diffstat (limited to 'test/hxdiff.zig')
| -rw-r--r-- | test/hxdiff.zig | 163 |
1 files changed, 111 insertions, 52 deletions
diff --git a/test/hxdiff.zig b/test/hxdiff.zig index f6ab5c6f..599390d6 100644 --- a/test/hxdiff.zig +++ b/test/hxdiff.zig @@ -90,7 +90,8 @@ pub fn main(init: std.process.Init) !void { var parity = false; var goldens_path: ?[]const u8 = null; var waivers_path: ?[]const u8 = null; - var case_paths: std.ArrayList([]const u8) = .empty; + const case_paths = arena.alloc([]const u8, args.len - 1) catch fatal("oom", .{}); + var n_case_paths: usize = 0; var want_waivers = false; for (args[1..]) |a| { if (want_waivers) { @@ -100,17 +101,18 @@ pub fn main(init: std.process.Init) !void { parity = true; } else if (std.mem.eql(u8, a, "--waivers")) { want_waivers = true; - } else if (parity or case_paths.items.len == 0) { + } else if (parity or n_case_paths == 0) { // diff mode is positional (cases, goldens, waivers); parity mode // takes any number of case files and nothing else - case_paths.append(arena, a) catch fatal("oom", .{}); + case_paths[n_case_paths] = a; + n_case_paths += 1; } else if (goldens_path == null) { goldens_path = a; } else if (waivers_path == null) { waivers_path = a; } else fatal("usage: pardes-hxdiff <cases.jsonl> [goldens.jsonl [waivers.jsonl]]", .{}); } - if (case_paths.items.len == 0) + if (n_case_paths == 0) fatal("usage: pardes-hxdiff [--parity] <cases.jsonl>... [goldens.jsonl [waivers.jsonl]]", .{}); // goldens + waivers, keyed by name (diff mode) @@ -144,11 +146,11 @@ pub fn main(init: std.process.Init) !void { } } - var out: std.ArrayList(u8) = .empty; + const stdout = std.Io.File.stdout(); var n_cases: usize = 0; var n_bad: usize = 0; var n_waived: usize = 0; - for (case_paths.items) |path| { + for (case_paths[0..n_case_paths]) |path| { const src = readFileAlloc(arena, path) catch |e| fatal("read {s}: {s}", .{ path, @errorName(e) }); var lines = std.mem.splitScalar(u8, src, '\n'); var lineno: usize = 0; @@ -179,8 +181,8 @@ pub fn main(init: std.process.Init) !void { // null sels/primary are DROPPED, not written: that is what // keeps a one-selection line identical to the old contract const jl = std.json.Stringify.valueAlloc(arena, res, .{ .emit_null_optional_fields = false }) catch fatal("oom", .{}); - out.appendSlice(arena, jl) catch fatal("oom", .{}); - out.append(arena, '\n') catch fatal("oom", .{}); + try stdout.writeStreamingAll(init.io, jl); + try stdout.writeStreamingAll(init.io, "\n"); continue; } const g = goldens.get(case.name) orelse fatal("no golden for case {s} (regen goldens?)", .{case.name}); @@ -195,10 +197,7 @@ pub fn main(init: std.process.Init) !void { std.debug.print("MISMATCH {s} (keys: {s})\n{s}", .{ case.name, case.keys, diff }); } } - if (!parity and goldens_path == null) { - try std.Io.File.stdout().writeStreamingAll(init.io, out.items); - return; - } + if (!parity and goldens_path == null) return; std.debug.print("{s}: {d} cases, {d} mismatches, {d} waived\n", .{ if (parity) "hxparity" else "hxdiff", n_cases, n_bad, n_waived }); if (n_bad > 0) std.process.exit(1); } @@ -235,18 +234,23 @@ const Waiver = struct { /// field-by-field compare; returns a human-readable report ("" = match). /// `wl`/`gl` name the two sides (helix vs pardes, or file vs pty). fn diffResult(arena: std.mem.Allocator, want: Result, got: Result, wl: []const u8, gl: []const u8) []const u8 { - var buf: std.ArrayList(u8) = .empty; + var parts: [5][]const u8 = undefined; + var n: usize = 0; if (!std.mem.eql(u8, want.text, got.text)) { - appendf(arena, &buf, " text: {s} {f} != {s} {f}\n", .{ wl, std.json.fmt(want.text, .{}), gl, std.json.fmt(got.text, .{}) }); + parts[n] = std.fmt.allocPrint(arena, " text: {s} {f} != {s} {f}\n", .{ wl, std.json.fmt(want.text, .{}), gl, std.json.fmt(got.text, .{}) }) catch ""; + n += 1; } if (!std.mem.eql(u8, want.mode, got.mode)) { - appendf(arena, &buf, " mode: {s} {s} != {s} {s}\n", .{ wl, want.mode, gl, got.mode }); + parts[n] = std.fmt.allocPrint(arena, " mode: {s} {s} != {s} {s}\n", .{ wl, want.mode, gl, got.mode }) catch ""; + n += 1; } if (want.cursor.row != got.cursor.row or want.cursor.col != got.cursor.col) { - appendf(arena, &buf, " cursor: {s} ({d},{d}) != {s} ({d},{d})\n", .{ wl, @as(i64, want.cursor.row), @as(i64, want.cursor.col), gl, @as(i64, got.cursor.row), @as(i64, got.cursor.col) }); + parts[n] = std.fmt.allocPrint(arena, " cursor: {s} ({d},{d}) != {s} ({d},{d})\n", .{ wl, @as(i64, want.cursor.row), @as(i64, want.cursor.col), gl, @as(i64, got.cursor.row), @as(i64, got.cursor.col) }) catch ""; + n += 1; } if (want.anchor.row != got.anchor.row or want.anchor.col != got.anchor.col) { - appendf(arena, &buf, " anchor: {s} ({d},{d}) != {s} ({d},{d})\n", .{ wl, @as(i64, want.anchor.row), @as(i64, want.anchor.col), gl, @as(i64, got.anchor.row), @as(i64, got.anchor.col) }); + parts[n] = std.fmt.allocPrint(arena, " anchor: {s} ({d},{d}) != {s} ({d},{d})\n", .{ wl, @as(i64, want.anchor.row), @as(i64, want.anchor.col), gl, @as(i64, got.anchor.row), @as(i64, got.anchor.col) }) catch ""; + n += 1; } // the whole selection: absent on both sides = one range, and the two // fields above already said everything about it @@ -258,31 +262,58 @@ fn diffResult(arena: std.mem.Allocator, want: Result, got: Result, wl: []const u a.anchor.row != b.anchor.row or a.anchor.col != b.anchor.col) sel_diff = true; }; if (sel_diff) { - appendf(arena, &buf, " sels: {s} {s} != {s} {s}\n", .{ wl, fmtSels(arena, ws, want.primary), gl, fmtSels(arena, gs, got.primary) }); + parts[n] = std.fmt.allocPrint(arena, " sels: {s} {s} != {s} {s}\n", .{ wl, fmtSels(arena, ws, want.primary), gl, fmtSels(arena, gs, got.primary) }) catch ""; + n += 1; } - return buf.items; + return concatParts(arena, parts[0..n]); } /// "2 of [(0,1)|(1,1) *(2,1)]" — every range as cursor|anchor (collapsed to /// one pair when they are the same cell), the primary starred fn fmtSels(arena: std.mem.Allocator, sels: []const Sel, primary: ?usize) []const u8 { if (sels.len == 0) return "<one>"; - var buf: std.ArrayList(u8) = .empty; - appendf(arena, &buf, "{d} of [", .{sels.len}); + var part_count: usize = 2; for (sels, 0..) |s, i| { - if (i > 0) appendf(arena, &buf, " ", .{}); - if (i == (primary orelse 0)) appendf(arena, &buf, "*", .{}); - appendf(arena, &buf, "({d},{d})", .{ @as(i64, s.cursor.row), @as(i64, s.cursor.col) }); - if (s.anchor.row != s.cursor.row or s.anchor.col != s.cursor.col) - appendf(arena, &buf, "|({d},{d})", .{ @as(i64, s.anchor.row), @as(i64, s.anchor.col) }); + part_count += 1; + if (i > 0) part_count += 1; + if (i == (primary orelse 0)) part_count += 1; + if (s.anchor.row != s.cursor.row or s.anchor.col != s.cursor.col) part_count += 1; } - appendf(arena, &buf, "]", .{}); - return buf.items; + const parts = arena.alloc([]const u8, part_count) catch return ""; + var n: usize = 0; + parts[n] = std.fmt.allocPrint(arena, "{d} of [", .{sels.len}) catch ""; + n += 1; + for (sels, 0..) |s, i| { + if (i > 0) { + parts[n] = " "; + n += 1; + } + if (i == (primary orelse 0)) { + parts[n] = "*"; + n += 1; + } + parts[n] = std.fmt.allocPrint(arena, "({d},{d})", .{ @as(i64, s.cursor.row), @as(i64, s.cursor.col) }) catch ""; + n += 1; + if (s.anchor.row != s.cursor.row or s.anchor.col != s.cursor.col) { + parts[n] = std.fmt.allocPrint(arena, "|({d},{d})", .{ @as(i64, s.anchor.row), @as(i64, s.anchor.col) }) catch ""; + n += 1; + } + } + parts[n] = "]"; + n += 1; + return concatParts(arena, parts[0..n]); } -fn appendf(arena: std.mem.Allocator, buf: *std.ArrayList(u8), comptime fmt: []const u8, fargs: anytype) void { - const s = std.fmt.allocPrint(arena, fmt, fargs) catch return; - buf.appendSlice(arena, s) catch {}; +fn concatParts(arena: std.mem.Allocator, parts: []const []const u8) []const u8 { + var len: usize = 0; + for (parts) |part| len = std.math.add(usize, len, part.len) catch return ""; + const out = arena.alloc(u8, len) catch return ""; + var offset: usize = 0; + for (parts) |part| { + @memcpy(out[offset..][0..part.len], part); + offset += part.len; + } + return out; } fn runCase(arena: std.mem.Allocator, case: Case, pane_kind: []const u8) !Result { @@ -297,12 +328,18 @@ fn runCase(arena: std.mem.Allocator, case: Case, pane_kind: []const u8) !Result _ = try core.hxOpenFileContent(case.text); } else if (std.mem.eql(u8, pane_kind, "tty")) { // the harness is the pty here: cook \n to \r\n the way ONLCR would - var cooked: std.ArrayList(u8) = .empty; + const cooked_len = std.math.add(usize, case.text.len, std.mem.count(u8, case.text, "\n")) catch return error.OutOfMemory; + const cooked = try arena.alloc(u8, cooked_len); + var cooked_i: usize = 0; for (case.text) |ch| { - if (ch == '\n') try cooked.append(arena, '\r'); - try cooked.append(arena, ch); + if (ch == '\n') { + cooked[cooked_i] = '\r'; + cooked_i += 1; + } + cooked[cooked_i] = ch; + cooked_i += 1; } - core.update(.{ .output = .{ .pane = 0, .bytes = cooked.items } }); + core.update(.{ .output = .{ .pane = 0, .bytes = cooked } }); pump(core); // leave tty mode the way a live session does: Ctrl-<tty_toggle>, // then park the cursor at (0,0) — the helix harness's initial @@ -400,12 +437,19 @@ fn runCase(arena: std.mem.Allocator, case: Case, pane_kind: []const u8) !Result // its line list is exactly a file content's — join it and you have the // same bytes, trailing newline included, with nothing to patch up. const pl = try core.paneCursorLines(pane); - var buf: std.ArrayList(u8) = .empty; + var text_len: usize = if (pl.lines.len == 0) 0 else pl.lines.len - 1; + for (pl.lines) |ln| text_len = std.math.add(usize, text_len, ln.len) catch return error.OutOfMemory; + const joined = try arena.alloc(u8, text_len); + var offset: usize = 0; for (pl.lines, 0..) |ln, i| { - if (i > 0) try buf.append(arena, '\n'); - try buf.appendSlice(arena, ln); + if (i > 0) { + joined[offset] = '\n'; + offset += 1; + } + @memcpy(joined[offset..][0..ln.len], ln); + offset += ln.len; } - text = buf.items; + text = joined; } var anchor: Pos = .{ .row = pane.cur_row, .col = pane.cur_col }; if (pane.vsel.active) { @@ -423,7 +467,8 @@ fn runCase(arena: std.mem.Allocator, case: Case, pane_kind: []const u8) !Result var sels: ?[]const Sel = null; var primary: ?usize = null; if (pane.nsel > 0) { - var out: std.ArrayList(Sel) = .empty; + const out = try arena.alloc(Sel, @as(usize, pane.nsel) + 1); + var out_len: usize = 0; const prim: Sel = .{ .cursor = .{ .row = pane.cur_row, .col = pane.cur_col }, .anchor = anchor }; const pr_row = @min(prim.cursor.row, prim.anchor.row); const pr_col = if (prim.cursor.row == prim.anchor.row) @min(prim.cursor.col, prim.anchor.col) else if (prim.cursor.row < prim.anchor.row) prim.cursor.col else prim.anchor.col; @@ -431,16 +476,18 @@ fn runCase(arena: std.mem.Allocator, case: Case, pane_kind: []const u8) !Result const s_row = @min(s.row, s.arow); const s_col = if (s.row == s.arow) @min(s.col, s.acol) else if (s.row < s.arow) s.col else s.acol; if (primary == null and (pr_row < s_row or (pr_row == s_row and pr_col <= s_col))) { - primary = out.items.len; - try out.append(arena, prim); + primary = out_len; + out[out_len] = prim; + out_len += 1; } - try out.append(arena, .{ .cursor = .{ .row = s.row, .col = s.col }, .anchor = .{ .row = s.arow, .col = s.acol } }); + out[out_len] = .{ .cursor = .{ .row = s.row, .col = s.col }, .anchor = .{ .row = s.arow, .col = s.acol } }; + out_len += 1; } if (primary == null) { - primary = out.items.len; - try out.append(arena, prim); + primary = out_len; + out[out_len] = prim; } - sels = out.items; + sels = out; } return .{ .name = case.name, @@ -464,18 +511,30 @@ fn readFileAlloc(arena: std.mem.Allocator, path: []const u8) ![]u8 { 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; + const end = libc.lseek(fd, 0, libc.SEEK.END); + if (end < 0 or libc.lseek(fd, 0, libc.SEEK.SET) < 0) return error.StatFailed; + const size = std.math.cast(usize, end) orelse return error.FileTooLarge; + const buf = try arena.alloc(u8, size); + var offset: usize = 0; + while (offset < buf.len) { + const n = libc.read(fd, buf[offset..].ptr, buf.len - offset); + if (n < 0) { + if (libc.errno(n) == .INTR) continue; + return error.ReadFailed; + } + if (n == 0) return buf[0..offset]; + offset += @intCast(n); + } + var extra: [1]u8 = undefined; while (true) { - const n = libc.read(fd, &chunk, chunk.len); + const n = libc.read(fd, &extra, extra.len); if (n < 0) { if (libc.errno(n) == .INTR) continue; return error.ReadFailed; } - if (n == 0) break; - try buf.appendSlice(arena, chunk[0..@intCast(n)]); + if (n > 0) return error.FileChanged; + return buf; } - return buf.items; } fn fatal(comptime fmt: []const u8, args: anytype) noreturn { |
