summaryrefslogtreecommitdiff
path: root/test/snapshot.zig
diff options
context:
space:
mode:
Diffstat (limited to 'test/snapshot.zig')
-rw-r--r--test/snapshot.zig372
1 files changed, 279 insertions, 93 deletions
diff --git a/test/snapshot.zig b/test/snapshot.zig
index 36a5b20b..c3611291 100644
--- a/test/snapshot.zig
+++ b/test/snapshot.zig
@@ -91,7 +91,7 @@ pub fn main(init: std.process.Init) !void {
// pass costs more than the extra parallelism saves.
const cpus = std.Thread.getCpuCount() catch 4;
var jobs: usize = cpus + cpus / 4;
- var scripts: std.ArrayList([]const u8) = .empty;
+ var script_count: usize = 0;
for (args[1..]) |a| {
if (std.mem.eql(u8, a, "--update")) {
update = true;
@@ -102,28 +102,42 @@ pub fn main(init: std.process.Init) !void {
} else if (exe == null) {
exe = try absPath(arena, orig_cwd, a);
} else {
- try scripts.append(arena, try absPath(arena, orig_cwd, a));
+ script_count += 1;
}
}
+ var scripts = try arena.alloc([]const u8, script_count);
+ var script_i: usize = 0;
+ var saw_exe = false;
+ for (args[1..]) |a| {
+ if (std.mem.eql(u8, a, "--update") or
+ std.mem.eql(u8, a, "--trace-stable") or
+ std.mem.startsWith(u8, a, "--jobs=")) continue;
+ if (!saw_exe) {
+ saw_exe = true;
+ continue;
+ }
+ scripts[script_i] = try absPath(arena, orig_cwd, a);
+ script_i += 1;
+ }
if (exe == null) fatal("usage: pardes-snap <exe> [scripts...] [--update] [--jobs=N]", .{});
const exe_z = try arena.dupeZ(u8, exe.?);
- if (scripts.items.len == 0) {
- try listSnaps(arena, try absPath(arena, orig_cwd, "test/snapshots"), &scripts);
- std.mem.sort([]const u8, scripts.items, {}, struct {
+ if (scripts.len == 0) {
+ scripts = try listSnaps(arena, try absPath(arena, orig_cwd, "test/snapshots"));
+ std.mem.sort([]const u8, scripts, {}, struct {
fn lt(_: void, x: []const u8, y: []const u8) bool {
return std.mem.lessThan(u8, x, y);
}
}.lt);
}
- if (scripts.items.len == 0) fatal("no .snap scripts found", .{});
+ if (scripts.len == 0) fatal("no .snap scripts found", .{});
try mkdir(arena, SNAP_BASE, true);
// one run at a time (this is the flock that used to be typed by hand)
_ = lockOrWait(SNAP_BASE ++ "/.run.lock", "another pardes-snap run");
- const codes = try arena.alloc(u8, scripts.items.len);
- try runBatch(arena, exe_z, toBatch(arena, scripts.items), update, jobs, codes);
+ const codes = try arena.alloc(u8, scripts.len);
+ try runBatch(arena, exe_z, toBatch(arena, scripts), update, jobs, codes);
// A failure in the parallel pass is more often contention than regression:
// the app can lose a race with a kworker while 20 siblings fight for the
@@ -135,11 +149,21 @@ pub fn main(init: std.process.Init) !void {
if (jobs > 1) {
var round: usize = 0;
while (round < 2) : (round += 1) {
- var retry: std.ArrayList(usize) = .empty;
- for (codes, 0..) |c, i| if (c != 0) try retry.append(arena, i);
- if (retry.items.len == 0 or retry.items.len > 10) break;
- const paths = try arena.alloc(Idx, retry.items.len);
- for (retry.items, paths, 0..) |i, *p, n| p.* = .{ .path = scripts.items[i], .code_idx = n };
+ var retry_count: usize = 0;
+ for (codes) |c| {
+ if (c != 0) retry_count += 1;
+ }
+ if (retry_count == 0 or retry_count > 10) break;
+ const retry = try arena.alloc(usize, retry_count);
+ var retry_i: usize = 0;
+ for (codes, 0..) |c, i| {
+ if (c != 0) {
+ retry[retry_i] = i;
+ retry_i += 1;
+ }
+ }
+ const paths = try arena.alloc(Idx, retry.len);
+ for (retry, paths, 0..) |i, *p, n| p.* = .{ .path = scripts[i], .code_idx = n };
std.debug.print("re-running {d} failed script(s) serially{s}:", .{
paths.len,
if (round == 0) "" else " with wider settle probes",
@@ -149,12 +173,12 @@ pub fn main(init: std.process.Init) !void {
careful = round > 0;
const recodes = try arena.alloc(u8, paths.len);
try runBatch(arena, exe_z, paths, update, 1, recodes);
- for (retry.items, recodes) |i, c| codes[i] = c;
+ for (retry, recodes) |i, c| codes[i] = c;
}
}
var failed: usize = 0;
- for (scripts.items, codes) |script_path, code| {
+ for (scripts, codes) |script_path, code| {
const report = readFileAlloc(arena, reportPath(arena, script_path) catch "") catch "";
std.debug.print("{s}", .{report});
if (code != 0) {
@@ -164,10 +188,10 @@ pub fn main(init: std.process.Init) !void {
}
}
if (failed > 0) {
- std.debug.print("{d}/{d} snapshot scripts FAILED\n", .{ failed, scripts.items.len });
+ std.debug.print("{d}/{d} snapshot scripts FAILED\n", .{ failed, scripts.len });
std.process.exit(1);
}
- std.debug.print("all {d} snapshot scripts ok\n", .{scripts.items.len});
+ std.debug.print("all {d} snapshot scripts ok\n", .{scripts.len});
}
/// Run `scripts` `jobs` at a time, one forked child each, filling `codes` with
@@ -198,24 +222,26 @@ fn runBatch(
codes: []u8,
) !void {
const Job = struct { pid: libc.pid_t, idx: usize };
- var running: std.ArrayList(Job) = .empty;
- defer running.deinit(arena);
+ const running = try arena.alloc(Job, @min(jobs, scripts.len));
+ var running_len: usize = 0;
var next: usize = 0;
- while (next < scripts.len or running.items.len > 0) {
- while (next < scripts.len and running.items.len < jobs) : (next += 1) {
+ while (next < scripts.len or running_len > 0) {
+ while (next < scripts.len and running_len < running.len) : (next += 1) {
const idx = next;
const pid = libc.fork();
if (pid == 0) std.process.exit(runOne(arena, exe_z, scripts[idx].path, update));
if (pid < 0) fatal("fork failed", .{});
- try running.append(arena, .{ .pid = pid, .idx = scripts[idx].code_idx });
+ running[running_len] = .{ .pid = pid, .idx = scripts[idx].code_idx };
+ running_len += 1;
}
var status: c_int = 0;
const pid = libc.waitpid(-1, &status, 0);
- for (running.items, 0..) |j, i| {
+ for (running[0..running_len], 0..) |j, i| {
if (j.pid != pid) continue;
// a killed child (crash in the harness itself) counts as a failure
codes[j.idx] = if (status == 0) 0 else 1;
- _ = running.swapRemove(i);
+ running_len -= 1;
+ running[i] = running[running_len];
break;
}
}
@@ -337,7 +363,8 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const
_ = setenv("PARDES_DUMP", try arena.dupeZ(u8, dump_path), 1);
if (chdirRc(try arena.dupeZ(u8, work)) < 0) return error.ChdirFailed;
- var out: std.ArrayList(u8) = .empty;
+ const chunks = try arena.alloc([]const u8, countCaptures(src));
+ var chunk_len: usize = 0;
var h: ?eh.Harness = null;
defer if (h) |*hp| hp.deinit();
@@ -368,9 +395,8 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const
// one) — a file that is long AND wide, which is what a horizontal
// scroll test needs and `file` can only give as one huge script line
const tail = tok.rest();
- var buf: std.ArrayList(u8) = .empty;
- for (1..n + 1) |i| try buf.appendSlice(arena, try std.fmt.allocPrint(arena, "line {d}{s}\n", .{ i, tail }));
- try eh.writeFile(try arena.dupeZ(u8, name), buf.items);
+ const buf = try numberedLines(arena, n, tail);
+ try eh.writeFile(try arena.dupeZ(u8, name), buf);
} else if (std.mem.eql(u8, cmd, "ppmdiag")) {
// The diagonal-split test PPM from the old e2e (white over blue),
// grown to 512x512 with the white half filled by a fixed LCG
@@ -381,19 +407,8 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const
// the art, which a 64x64 image cannot show (its cells average
// down to flat color and a block always wins).
const name = tok.next() orelse return error.BadScript;
- var ppm: std.ArrayList(u8) = .empty;
- try ppm.appendSlice(arena, "P6\n512 512\n255\n");
- var seed: u64 = 12345;
- var py: usize = 0;
- while (py < 512) : (py += 1) {
- var px: usize = 0;
- while (px < 512) : (px += 1) {
- seed = seed *% 1103515245 +% 12345;
- const v: u8 = @truncate(seed >> 16);
- try ppm.appendSlice(arena, if (px + py < 512) &[_]u8{ v, v, v } else &[_]u8{ 0, 0, 255 });
- }
- }
- try eh.writeFile(try arena.dupeZ(u8, name), ppm.items);
+ const ppm = try ppmDiag(arena);
+ try eh.writeFile(try arena.dupeZ(u8, name), ppm);
} else if (std.mem.eql(u8, cmd, "dirmk")) {
try mkdir(arena, tok.next() orelse return error.BadScript, false);
} else if (std.mem.eql(u8, cmd, "run")) {
@@ -418,22 +433,10 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const
} else if (std.mem.eql(u8, cmd, "snapfile")) {
const label = tok.next() orelse return error.BadScript;
const contents = try readFileAlloc(arena, tok.rest());
- try out.appendSlice(arena, try std.fmt.allocPrint(arena, "== file {s}\n", .{label}));
- // elide base64 payloads: they encode raw pty byte HISTORY, whose
+ // Elide base64 payloads: they encode raw pty byte HISTORY, whose
// micro-timing (prompt redraw cycles) legitimately differs between
// implementations; the cleaned text fields are the contract.
- var flines = std.mem.splitScalar(u8, contents, '\n');
- while (flines.next()) |fl| {
- if (std.mem.indexOf(u8, fl, "_b64 = \"") != null) {
- const eq = std.mem.indexOf(u8, fl, "=").?;
- try out.appendSlice(arena, fl[0 .. eq + 1]);
- try out.appendSlice(arena, " <elided>\n");
- } else {
- try out.appendSlice(arena, fl);
- try out.append(arena, '\n');
- }
- }
- try out.appendSlice(arena, "== end file\n");
+ try appendChunk(chunks, &chunk_len, try snapFile(arena, label, contents));
} else if (std.mem.eql(u8, cmd, "wait")) {
const ms = try std.fmt.parseInt(i64, tok.next() orelse return error.BadScript, 10);
try live(&h).expectWaitFor(tok.rest(), ms, "snapshot wait");
@@ -471,15 +474,119 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const
const cols = try std.fmt.parseInt(u16, tok.next() orelse return error.BadScript, 10);
try live(&h).resize(rows, cols);
} else if (std.mem.eql(u8, cmd, "snap")) {
- try snapText(arena, &out, live(&h), tok.rest());
+ try appendChunk(chunks, &chunk_len, try snapText(arena, live(&h), tok.rest()));
} else if (std.mem.eql(u8, cmd, "snapstyle")) {
- try snapStyle(arena, &out, live(&h), tok.rest());
+ try appendChunk(chunks, &chunk_len, try snapStyle(arena, live(&h), tok.rest()));
} else {
std.debug.print("unknown command {s}\n", .{cmd});
return error.BadScript;
}
}
- return out.items;
+ return concatChunks(arena, chunks[0..chunk_len]);
+}
+
+fn countCaptures(src: []const u8) usize {
+ var count: usize = 0;
+ var lines = std.mem.splitScalar(u8, src, '\n');
+ while (lines.next()) |raw_line| {
+ const line = std.mem.trim(u8, raw_line, " \t\r");
+ if (line.len == 0 or line[0] == '#') continue;
+ var tok = std.mem.tokenizeScalar(u8, line, ' ');
+ const cmd = tok.next().?;
+ if (std.mem.eql(u8, cmd, "snap") or
+ std.mem.eql(u8, cmd, "snapstyle") or
+ std.mem.eql(u8, cmd, "snapfile")) count += 1;
+ }
+ return count;
+}
+
+fn appendChunk(chunks: [][]const u8, len: *usize, chunk: []const u8) !void {
+ if (len.* == chunks.len) return error.BadScript;
+ chunks[len.*] = chunk;
+ len.* += 1;
+}
+
+fn concatChunks(arena: std.mem.Allocator, chunks: []const []const u8) ![]u8 {
+ var total: usize = 0;
+ for (chunks) |chunk| total += chunk.len;
+ const out = try arena.alloc(u8, total);
+ var pos: usize = 0;
+ for (chunks) |chunk| copyBytes(out, &pos, chunk);
+ return out;
+}
+
+fn copyBytes(out: []u8, pos: *usize, bytes: []const u8) void {
+ @memcpy(out[pos.*..][0..bytes.len], bytes);
+ pos.* += bytes.len;
+}
+
+fn decimalDigits(value: usize) usize {
+ var v = value;
+ var digits: usize = 1;
+ while (v >= 10) : (digits += 1) v /= 10;
+ return digits;
+}
+
+fn numberedLines(arena: std.mem.Allocator, n: usize, tail: []const u8) ![]u8 {
+ var total: usize = 0;
+ for (1..n + 1) |i| total += "line ".len + decimalDigits(i) + tail.len + 1;
+ const out = try arena.alloc(u8, total);
+ var pos: usize = 0;
+ for (1..n + 1) |i| {
+ const line = try std.fmt.bufPrint(out[pos..], "line {d}{s}\n", .{ i, tail });
+ pos += line.len;
+ }
+ return out;
+}
+
+fn ppmDiag(arena: std.mem.Allocator) ![]u8 {
+ const header = "P6\n512 512\n255\n";
+ const out = try arena.alloc(u8, header.len + 512 * 512 * 3);
+ @memcpy(out[0..header.len], header);
+ var pos = header.len;
+ var seed: u64 = 12345;
+ var py: usize = 0;
+ while (py < 512) : (py += 1) {
+ var px: usize = 0;
+ while (px < 512) : (px += 1) {
+ seed = seed *% 1103515245 +% 12345;
+ const v: u8 = @truncate(seed >> 16);
+ const pixel: [3]u8 = if (px + py < 512) .{ v, v, v } else .{ 0, 0, 255 };
+ @memcpy(out[pos..][0..pixel.len], &pixel);
+ pos += pixel.len;
+ }
+ }
+ return out;
+}
+
+fn snapFile(arena: std.mem.Allocator, label: []const u8, contents: []const u8) ![]u8 {
+ const header = try std.fmt.allocPrint(arena, "== file {s}\n", .{label});
+ var total = header.len + "== end file\n".len;
+ var lines = std.mem.splitScalar(u8, contents, '\n');
+ while (lines.next()) |line| {
+ if (std.mem.indexOf(u8, line, "_b64 = \"") != null) {
+ const eq = std.mem.indexOfScalar(u8, line, '=').?;
+ total += eq + 1 + " <elided>\n".len;
+ } else {
+ total += line.len + 1;
+ }
+ }
+ const out = try arena.alloc(u8, total);
+ var pos: usize = 0;
+ copyBytes(out, &pos, header);
+ lines = std.mem.splitScalar(u8, contents, '\n');
+ while (lines.next()) |line| {
+ if (std.mem.indexOf(u8, line, "_b64 = \"") != null) {
+ const eq = std.mem.indexOfScalar(u8, line, '=').?;
+ copyBytes(out, &pos, line[0 .. eq + 1]);
+ copyBytes(out, &pos, " <elided>\n");
+ } else {
+ copyBytes(out, &pos, line);
+ copyBytes(out, &pos, "\n");
+ }
+ }
+ copyBytes(out, &pos, "== end file\n");
+ return out;
}
fn live(h: *?eh.Harness) *eh.Harness {
@@ -649,48 +756,78 @@ fn stateKey(h: *eh.Harness) ![]u8 {
return std.fmt.allocPrint(h.gpa, "{d},{d},{x}\n{s}", .{ c.x, c.y, styles.final(), text });
}
-fn snapText(arena: std.mem.Allocator, out: *std.ArrayList(u8), h: *eh.Harness, label: []const u8) !void {
+fn snapText(arena: std.mem.Allocator, h: *eh.Harness, label: []const u8) ![]u8 {
const text = try h.screenText();
defer h.gpa.free(text);
const c = h.cursor();
- try out.appendSlice(arena, try std.fmt.allocPrint(arena, "== snap {s} grid={d}x{d} cursor={d},{d}\n", .{ label, h.cols, h.rows, c.x, c.y }));
+ const header = try std.fmt.allocPrint(arena, "== snap {s} grid={d}x{d} cursor={d},{d}\n", .{ label, h.cols, h.rows, c.x, c.y });
+ var total = header.len;
var it = std.mem.splitScalar(u8, text, '\n');
var row: usize = 0;
while (row < h.rows) : (row += 1) {
const line = it.next() orelse "";
- try out.append(arena, '|');
- try out.appendSlice(arena, std.mem.trimEnd(u8, line, " "));
- try out.append(arena, '\n');
+ total += 1 + std.mem.trimEnd(u8, line, " ").len + 1;
}
+ const out = try arena.alloc(u8, total);
+ var pos: usize = 0;
+ copyBytes(out, &pos, header);
+ it = std.mem.splitScalar(u8, text, '\n');
+ row = 0;
+ while (row < h.rows) : (row += 1) {
+ const line = it.next() orelse "";
+ copyBytes(out, &pos, "|");
+ copyBytes(out, &pos, std.mem.trimEnd(u8, line, " "));
+ copyBytes(out, &pos, "\n");
+ }
+ return out;
}
/// Per-row style runs from the harness emulator: `|y: x0-x1 fg,bg,attrs ...`.
/// fg/bg: d (default) | pN (palette) | #rrggbb. attrs: b f i k r v x + u U ~ . -
/// for underline single/double/curly/dotted/dashed.
-fn snapStyle(arena: std.mem.Allocator, out: *std.ArrayList(u8), h: *eh.Harness, label: []const u8) !void {
- try out.appendSlice(arena, try std.fmt.allocPrint(arena, "== style {s}\n", .{label}));
+fn snapStyle(arena: std.mem.Allocator, h: *eh.Harness, label: []const u8) ![]u8 {
+ const header = try std.fmt.allocPrint(arena, "== style {s}\n", .{label});
+ const rows_len = styleRows(h, null);
+ const out = try arena.alloc(u8, header.len + rows_len);
+ var pos: usize = 0;
+ copyBytes(out, &pos, header);
+ _ = styleRows(h, out[pos..]);
+ return out;
+}
+
+fn styleRows(h: *eh.Harness, dst: ?[]u8) usize {
+ var pos: usize = 0;
var y: u16 = 0;
while (y < h.rows) : (y += 1) {
- try out.appendSlice(arena, try std.fmt.allocPrint(arena, "|{d}:", .{y}));
- var run_buf: [64]u8 = undefined;
+ var fmt_buf: [64]u8 = undefined;
+ emitBytes(dst, &pos, std.fmt.bufPrint(&fmt_buf, "|{d}:", .{y}) catch unreachable);
var key_buf: [64]u8 = undefined;
- var run_key: []const u8 = "";
+ var run_key: [64]u8 = undefined;
+ var run_len: usize = 0;
var run_start: u16 = 0;
var x: u16 = 0;
while (x <= h.cols) : (x += 1) {
const key: []const u8 = if (x == h.cols) "\x00end" else cellKey(h, x, y, &key_buf);
if (x == 0) {
- run_key = try arena.dupe(u8, key);
+ @memcpy(run_key[0..key.len], key);
+ run_len = key.len;
continue;
}
- if (std.mem.eql(u8, key, run_key)) continue;
- try out.appendSlice(arena, try std.fmt.bufPrint(&run_buf, " {d}-{d} ", .{ run_start, x - 1 }));
- try out.appendSlice(arena, run_key);
- run_key = try arena.dupe(u8, key);
+ if (std.mem.eql(u8, key, run_key[0..run_len])) continue;
+ emitBytes(dst, &pos, std.fmt.bufPrint(&fmt_buf, " {d}-{d} ", .{ run_start, x - 1 }) catch unreachable);
+ emitBytes(dst, &pos, run_key[0..run_len]);
+ @memcpy(run_key[0..key.len], key);
+ run_len = key.len;
run_start = x;
}
- try out.append(arena, '\n');
+ emitBytes(dst, &pos, "\n");
}
+ return pos;
+}
+
+fn emitBytes(dst: ?[]u8, pos: *usize, bytes: []const u8) void {
+ if (dst) |out| @memcpy(out[pos.*..][0..bytes.len], bytes);
+ pos.* += bytes.len;
}
fn cellKey(h: *eh.Harness, x: u16, y: u16, buf: *[64]u8) []const u8 {
@@ -810,29 +947,51 @@ fn buttonCode(name: []const u8) ?u16 {
}
fn unescape(arena: std.mem.Allocator, s: []const u8) ![]const u8 {
- var buf: std.ArrayList(u8) = .empty;
+ var len: usize = 0;
var i: usize = 0;
while (i < s.len) : (i += 1) {
if (s[i] != '\\' or i + 1 >= s.len) {
- try buf.append(arena, s[i]);
+ len += 1;
continue;
}
i += 1;
switch (s[i]) {
- 'e' => try buf.append(arena, 0x1b),
- 'r' => try buf.append(arena, '\r'),
- 'n' => try buf.append(arena, '\n'),
- 't' => try buf.append(arena, '\t'),
- '\\' => try buf.append(arena, '\\'),
+ 'e', 'r', 'n', 't', '\\' => len += 1,
'x' => {
if (i + 2 >= s.len) return error.BadEscape;
- try buf.append(arena, try std.fmt.parseInt(u8, s[i + 1 .. i + 3], 16));
+ _ = try std.fmt.parseInt(u8, s[i + 1 .. i + 3], 16);
+ len += 1;
i += 2;
},
else => return error.BadEscape,
}
}
- return buf.items;
+ const out = try arena.alloc(u8, len);
+ var pos: usize = 0;
+ i = 0;
+ while (i < s.len) : (i += 1) {
+ if (s[i] != '\\' or i + 1 >= s.len) {
+ out[pos] = s[i];
+ pos += 1;
+ continue;
+ }
+ i += 1;
+ out[pos] = switch (s[i]) {
+ 'e' => 0x1b,
+ 'r' => '\r',
+ 'n' => '\n',
+ 't' => '\t',
+ '\\' => '\\',
+ 'x' => value: {
+ const value = try std.fmt.parseInt(u8, s[i + 1 .. i + 3], 16);
+ i += 2;
+ break :value value;
+ },
+ else => unreachable,
+ };
+ pos += 1;
+ }
+ return out;
}
fn absPath(arena: std.mem.Allocator, cwd: []const u8, p: []const u8) ![]const u8 {
@@ -840,15 +999,33 @@ fn absPath(arena: std.mem.Allocator, cwd: []const u8, p: []const u8) ![]const u8
return try std.fmt.allocPrint(arena, "{s}/{s}", .{ cwd, p });
}
-fn listSnaps(arena: std.mem.Allocator, dir_path: []const u8, scripts: *std.ArrayList([]const u8)) !void {
+fn listSnaps(arena: std.mem.Allocator, dir_path: []const u8) ![][]const u8 {
const dir_z = try arena.dupeZ(u8, dir_path);
+ const count = countSnapEntries(dir_z, dir_path);
+ const scripts = try arena.alloc([]const u8, count);
+ const dir = libc.opendir(dir_z) orelse fatal("no snapshots/ dir at {s}", .{dir_path});
+ defer _ = libc.closedir(dir);
+ var i: usize = 0;
+ 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")) continue;
+ if (i == scripts.len) return error.DirectoryChanged;
+ scripts[i] = try std.fmt.allocPrint(arena, "{s}/{s}", .{ dir_path, name });
+ i += 1;
+ }
+ if (i != scripts.len) return error.DirectoryChanged;
+ return scripts;
+}
+
+fn countSnapEntries(dir_z: [:0]const u8, dir_path: []const u8) usize {
const dir = libc.opendir(dir_z) orelse fatal("no snapshots/ dir at {s}", .{dir_path});
defer _ = libc.closedir(dir);
+ var count: usize = 0;
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 }));
+ if (std.mem.endsWith(u8, name, ".snap")) count += 1;
}
+ return count;
}
fn readFileAlloc(arena: std.mem.Allocator, path: []const u8) ![]u8 {
@@ -856,18 +1033,27 @@ 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;
- while (true) {
- const n = libc.read(fd, &chunk, chunk.len);
+ const end = libc.lseek(fd, 0, libc.SEEK.END);
+ if (end < 0 or libc.lseek(fd, 0, libc.SEEK.SET) < 0) return error.ReadFailed;
+ const buf = try arena.alloc(u8, @intCast(end));
+ var pos: usize = 0;
+ while (pos < buf.len) {
+ const n = libc.read(fd, buf[pos..].ptr, buf.len - pos);
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.ReadFailed;
+ pos += @intCast(n);
+ }
+ var extra: [1]u8 = undefined;
+ while (true) {
+ const n = libc.read(fd, &extra, extra.len);
+ if (n < 0 and libc.errno(n) == .INTR) continue;
+ if (n != 0) return error.ReadFailed;
+ break;
}
- return buf.items;
+ return buf;
}
fn mkdir(arena: std.mem.Allocator, path: []const u8, exist_ok: bool) !void {