1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
|
const std = @import("std");
const pardes = @import("pardes");
const filesystem = pardes.filesystem;
test "runtime Mount and Unmount copy command arguments and change Look resolution" {
const gpa = std.testing.allocator;
const p = try pardes.Pardes.init(gpa, .{ .tty_only = true });
defer p.deinit();
var command = "Mount peer /tmp/a socket.sock".*;
try std.testing.expect(p.executeBuiltinLine(0, &command));
@memset(&command, 'x');
try std.testing.expectEqual(@as(usize, 1), p.fs.mounts.items.len);
try std.testing.expectEqualStrings("peer", p.fs.mounts.items[0].name);
try std.testing.expectEqualStrings("/tmp/a socket.sock", p.fs.mounts.items[0].dial);
var path: [4096]u8 = undefined;
try std.testing.expectEqualStrings("/n/peer/self/index", filesystem.resolve(p, "/n/peer/self/index", "/", &path).?.path);
try std.testing.expect(p.executeBuiltinLine(0, "Mount peer /tmp/other.sock"));
try std.testing.expectEqual(@as(usize, 1), p.fs.mounts.items.len);
try std.testing.expectEqualStrings("/tmp/a socket.sock", p.fs.mounts.items[0].dial);
try std.testing.expect(p.executeBuiltinLine(0, "Unmount peer"));
try std.testing.expectEqual(@as(usize, 0), p.fs.mounts.items.len);
try std.testing.expect(filesystem.resolve(p, "/n/peer/self/index", "/", &path) == null);
for ([_][]const u8{ "Mount", "Mount name", "Mount os /tmp/peer.sock", "Mount peer tcp!127.0.0.1!0", "Unmount", "Unmount name extra" }) |invalid| {
try std.testing.expect(p.executeBuiltinLine(0, invalid));
try std.testing.expectEqual(@as(usize, 0), p.fs.mounts.items.len);
}
}
test "mounts survive dumps without borrowing inputs or silently overriding a saved target" {
const gpa = std.testing.allocator;
var name = "peer".*;
var dial = "/tmp/peer.sock".*;
const startup = [_]filesystem.Mount{.{ .name = &name, .dial = &dial }};
const p = try pardes.Pardes.init(gpa, .{ .tty_only = true, .mounts = &startup });
defer p.deinit();
@memset(&name, 'x');
@memset(&dial, 'x');
try std.testing.expectEqual(@as(usize, 0), p.opts.mounts.len);
const file = try p.setTestFile("saved remote body\n");
file.file.?.revision = 3;
gpa.free(file.file.?.path);
file.file.?.path = try gpa.dupe(u8, "/n/peer/os/file.txt");
try p.dumpState();
const restored = try pardes.Pardes.initFromDump(gpa, .{}, p.dump_out.?);
defer restored.deinit();
try std.testing.expectEqual(@as(usize, 1), restored.fs.mounts.items.len);
try std.testing.expectEqualStrings("peer", restored.fs.mounts.items[0].name);
try std.testing.expectEqualStrings("/tmp/peer.sock", restored.fs.mounts.items[0].dial);
try std.testing.expectEqualStrings("/n/peer/os/file.txt", restored.panes[0].?.file.?.path);
try std.testing.expectEqualStrings("saved remote body\n", restored.panes[0].?.file.?.content);
try std.testing.expect(restored.panes[0].?.file.?.revision != restored.panes[0].?.file.?.saved_revision);
try std.testing.expectError(error.MountInUse, filesystem.unmount(restored, "peer"));
const same = try pardes.Pardes.initFromDump(gpa, .{ .mounts = &.{.{ .name = "peer", .dial = "/tmp/peer.sock" }} }, p.dump_out.?);
defer same.deinit();
try std.testing.expectEqual(@as(usize, 1), same.fs.mounts.items.len);
try std.testing.expectError(error.MountConflict, pardes.Pardes.initFromDump(gpa, .{
.mounts = &.{.{ .name = "peer", .dial = "/tmp/different.sock" }},
}, p.dump_out.?));
const added = try pardes.Pardes.initFromDump(gpa, .{ .mounts = &.{.{ .name = "other", .dial = "/tmp/other.sock" }} }, p.dump_out.?);
defer added.deinit();
try std.testing.expectEqual(@as(usize, 2), added.fs.mounts.items.len);
}
test "removed startup mounts are not resurrected by dump restore" {
const gpa = std.testing.allocator;
const p = try pardes.Pardes.init(gpa, .{
.tty_only = true,
.mounts = &.{.{ .name = "peer", .dial = "/tmp/peer.sock" }},
});
defer p.deinit();
try filesystem.unmount(p, "peer");
try p.dumpState();
const restored = try pardes.Pardes.initFromDump(gpa, p.opts, p.dump_out.?);
defer restored.deinit();
try std.testing.expectEqual(@as(usize, 0), restored.fs.mounts.items.len);
}
test "transactional restore preserves host capabilities and advances pane identities" {
const gpa = std.testing.allocator;
const p = try pardes.Pardes.init(gpa, .{ .tty_only = true, .cols = 91, .rows = 37 });
defer p.deinit();
const pane = try p.setTestFile("before\n");
p.lsp_seq = 41;
p.pipe_seq = 73;
if (@hasField(pardes.CellPixels, "w")) p.cell_pixels = .{ .w = 9, .h = 18 };
p.native_images = true;
p.fs.socket_path = "/tmp/session.sock";
p.fs.tcp_address = .{ .ip4 = .loopback(5640) };
p.fs.quic_address = .{ .ip6 = .loopback(5641) };
try p.dumpState();
const restored = try p.restore(p.dump_out.?);
defer restored.deinit();
try std.testing.expectEqual(@as(u32, 41), restored.lsp_seq);
try std.testing.expectEqual(@as(u32, 73), restored.pipe_seq);
try std.testing.expectEqual(p.cell_pixels, restored.cell_pixels);
try std.testing.expect(restored.native_images);
try std.testing.expect(restored.panes[0].?.serial > p.next_serial);
try std.testing.expect(restored.paneBySerial(pane.serial) == null);
try std.testing.expectEqual(p.screen_w, restored.screen_w);
try std.testing.expectEqual(p.screen_h, restored.screen_h);
try std.testing.expectEqualStrings(p.fs.socket_path, restored.fs.socket_path);
try std.testing.expectEqualDeep(p.fs.tcp_address, restored.fs.tcp_address);
try std.testing.expectEqualDeep(p.fs.quic_address, restored.fs.quic_address);
try std.testing.expect(p.panes[0] == pane);
try std.testing.expectEqualStrings("before\n", pane.file.?.content);
try std.testing.expectError(error.BadDumpMagic, p.restore(
".{ .magic = \"wrong\", .screen = .{.cols = 80, .rows = 24} }",
));
try std.testing.expect(p.panes[0] == pane);
try std.testing.expectEqualStrings("before\n", pane.file.?.content);
try std.testing.expectEqual(@as(u32, 41), p.lsp_seq);
try std.testing.expectEqualStrings("/tmp/session.sock", p.fs.socket_path);
}
test "same-session identity is available before an initial mounted file is opened" {
if (!filesystem.platform_has_fs) return error.SkipZigTest;
const gpa = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "file.txt", .data = "no socket required\n" });
var buf: [4096]u8 = undefined;
const len = try tmp.dir.realPathFile(std.testing.io, "file.txt", &buf);
const path = try std.fmt.allocPrint(gpa, "/n/own/os{s}", .{buf[0..len]});
defer gpa.free(path);
const p = try pardes.Pardes.init(gpa, .{
.file = path,
.mounts = &.{.{ .name = "own", .dial = "/tmp/not-listening.sock" }},
.ninep_identity = .{ .socket_path = "/tmp/not-listening.sock" },
});
defer p.deinit();
try std.testing.expectEqualStrings(path, p.panes[0].?.file.?.path);
try std.testing.expectEqualStrings("no socket required\n", p.panes[0].?.file.?.content);
try std.testing.expectEqualStrings("", p.opts.ninep_identity.socket_path);
}
test "filesystem resolves OS first then virtual with explicit mounts and parent paths" {
if (!filesystem.platform_has_fs) return error.SkipZigTest;
const gpa = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "index", .data = "OS INDEX\n" });
var directory_buffer: [4096]u8 = undefined;
const directory_len = try tmp.dir.realPath(std.testing.io, &directory_buffer);
const directory = directory_buffer[0..directory_len];
const os_index = try std.fmt.allocPrint(gpa, "{s}/index", .{directory});
defer gpa.free(os_index);
const explicit_os = try std.fmt.allocPrint(gpa, "/n/os{s}", .{os_index});
defer gpa.free(explicit_os);
const p = try pardes.Pardes.init(gpa, .{ .tty_only = true });
defer p.deinit();
const pane = try p.setTestFile("SELF BODY\n");
const explicit_self = try std.fmt.allocPrint(gpa, "/n/self/pane/{d}/body", .{pane.serial});
defer gpa.free(explicit_self);
const virtual_self = try std.fmt.allocPrint(gpa, "/virtual/pane/{d}/body", .{pane.serial});
defer gpa.free(virtual_self);
const parent_self = try std.fmt.allocPrint(gpa, "/n/self/pane/{d}/../{d}/body", .{ pane.serial, pane.serial });
defer gpa.free(parent_self);
for ([_]struct { word: []const u8, path: []const u8, bytes: []const u8 }{
.{ .word = "index", .path = os_index, .bytes = "OS INDEX\n" },
.{ .word = os_index, .path = os_index, .bytes = "OS INDEX\n" },
.{ .word = explicit_os, .path = explicit_os, .bytes = "OS INDEX\n" },
.{ .word = explicit_self, .path = virtual_self, .bytes = "SELF BODY\n" },
.{ .word = virtual_self, .path = virtual_self, .bytes = "SELF BODY\n" },
.{ .word = parent_self, .path = virtual_self, .bytes = "SELF BODY\n" },
}) |case| {
var resolved_buffer: [4096]u8 = undefined;
const found = filesystem.resolve(p, case.word, directory, &resolved_buffer) orelse return error.UnresolvedPath;
try std.testing.expectEqualStrings(case.path, found.path);
const bytes = try filesystem.read(p, found.path);
defer gpa.free(bytes);
try std.testing.expectEqualStrings(case.bytes, bytes);
}
for ([_][]const u8{ "/n/self/index", "/virtual/index" }) |word| {
var resolved_buffer: [4096]u8 = undefined;
const found = filesystem.resolve(p, word, directory, &resolved_buffer) orelse return error.UnresolvedPath;
try std.testing.expectEqualStrings("/virtual/index", found.path);
const bytes = try filesystem.read(p, found.path);
defer gpa.free(bytes);
try std.testing.expect(std.mem.indexOf(u8, bytes, "/test.txt") != null);
}
try tmp.dir.deleteFile(std.testing.io, "index");
var resolved_buffer: [4096]u8 = undefined;
const fallback = filesystem.resolve(p, "index", directory, &resolved_buffer) orelse return error.MissingVirtualFallback;
try std.testing.expectEqualStrings("/virtual/index", fallback.path);
for ([_][]const u8{ "src/pardes.zig", "/virtual/src/pardes.zig", "/virtual/src/../src/pardes.zig" }) |word| {
const found = filesystem.resolve(p, word, directory, &resolved_buffer) orelse return error.MissingEmbeddedSource;
const bytes = try filesystem.read(p, found.path);
defer gpa.free(bytes);
try std.testing.expect(std.mem.indexOf(u8, bytes, "pub const Pardes") != null);
}
for ([_][]const u8{ "/virtual/src", "/n/self/src" }) |word| {
const found = filesystem.resolve(p, word, directory, &resolved_buffer) orelse return error.MissingEmbeddedDirectory;
const bytes = try filesystem.read(p, found.path);
defer gpa.free(bytes);
try std.testing.expect(std.mem.indexOf(u8, bytes, "/virtual/src/pardes.zig\n") != null);
}
}
test "explicit OS file opens and restores keep their names and request watches" {
if (!filesystem.platform_has_fs) return error.SkipZigTest;
const gpa = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "watched.zig", .data = "const disk = 1;\n" });
var directory_buffer: [4096]u8 = undefined;
const directory_len = try tmp.dir.realPath(std.testing.io, &directory_buffer);
const path = try std.fmt.allocPrint(gpa, "/n/os{s}/watched.zig", .{directory_buffer[0..directory_len]});
defer gpa.free(path);
const p = try pardes.Pardes.init(gpa, .{ .tty_only = true });
defer p.deinit();
while (p.nextEffect()) |_| {}
const opened = p.freeSlot().?;
const pane = try pardes.panes.File.open(p, opened, path, 0);
try std.testing.expectEqualStrings(path, pane.file.?.path);
try std.testing.expectEqualStrings("const disk = 1;\n", pane.file.?.content);
const restored = p.freeSlot().?;
const copy = try pardes.panes.File.restore(p, restored, .{
.kind = .file,
.tag = path,
.body = "",
.file = .{ .path = path, .content = "const unsaved = 2;\n" },
});
try std.testing.expectEqualStrings(path, copy.file.?.path);
try std.testing.expectEqualStrings("const unsaved = 2;\n", copy.file.?.content);
var watched_open = false;
var watched_restore = false;
while (p.nextEffect()) |effect| if (effect == .watch and effect.watch.on) {
if (effect.watch.pane == opened) watched_open = true;
if (effect.watch.pane == restored) watched_restore = true;
};
try std.testing.expect(watched_open and watched_restore);
}
test "virtual file opens do not request OS watches" {
const p = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
while (p.nextEffect()) |_| {}
const free = p.freeSlot().?;
_ = try pardes.panes.File.open(p, free, "/virtual/src/pardes.zig", 0);
while (p.nextEffect()) |effect| if (effect == .watch) return error.VirtualFileWatch;
}
test "Look at the self factory reserves its own pane and failed opens release it" {
const p = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
p.lookAt(0, "/virtual/new/ctl");
var control_count: usize = 0;
var scratch_count: usize = 0;
for (p.panes) |slot| {
const pane = slot orelse continue;
const file = pane.file orelse continue;
if (std.mem.eql(u8, file.path, "/virtual/new/ctl")) control_count += 1;
if (file.output) |output| if (output.from == .cmd and output.from.cmd == .New) {
scratch_count += 1;
};
}
try std.testing.expectEqual(@as(usize, 1), control_count);
try std.testing.expectEqual(@as(usize, 1), scratch_count);
const free = p.freeSlot().?;
try std.testing.expectError(error.FileNotFound, pardes.panes.File.open(p, free, "/n/self/does-not-exist", 0));
try std.testing.expect(p.panes[free] == null);
try std.testing.expect(!p.reserved_slots[free]);
_ = try p.newDocPane(free);
}
test "owned cwd failed file opens never publish a pane or retain a reservation" {
var succeeded = false;
for (0..16) |failure| {
var allocator = std.testing.FailingAllocator.init(std.testing.allocator, .{});
const p = try pardes.Pardes.init(allocator.allocator(), .{ .tty_only = true });
defer p.deinit();
const source = try p.setTestFile("source body\n");
p.setCwd(0, "/source/directory");
while (p.nextEffect()) |_| {}
var path: [128]u8 = undefined;
const name = try std.fmt.bufPrint(&path, "/virtual/pane/{d}/body", .{source.serial});
const slot = p.freeSlot().?;
const serial = p.next_serial;
const columns = p.col_panes;
const counts = p.col_n;
allocator.fail_index = allocator.alloc_index + failure;
const opened = pardes.panes.File.open(p, slot, name, 0) catch |err| {
try std.testing.expect(err == error.OutOfMemory or err == error.ReadFailed or err == error.WriteFailed);
try std.testing.expect(p.panes[slot] == null);
try std.testing.expect(!p.reserved_slots[slot]);
try std.testing.expectEqual(serial, p.next_serial);
try std.testing.expectEqual(slot, p.freeSlot().?);
try std.testing.expectEqualDeep(columns, p.col_panes);
try std.testing.expectEqualDeep(counts, p.col_n);
try std.testing.expectEqual(@as(usize, 0), p.effects_len);
try std.testing.expectEqualStrings("/source/directory", source.cwdSlice());
allocator.fail_index = std.math.maxInt(usize);
_ = try p.newDocPane(slot);
continue;
};
try std.testing.expect(!p.reserved_slots[slot]);
try std.testing.expectEqualStrings("source body\n", opened.file.?.content);
succeeded = true;
break;
}
try std.testing.expect(succeeded);
}
test "images decode and dump through explicit filesystem mounts" {
if (!filesystem.platform_has_fs) return error.SkipZigTest;
const gpa = std.testing.allocator;
pardes.image.start(std.testing.io, gpa);
defer pardes.image.stop();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const bytes = "P6\n1 1\n255\n\x0d\x4d\xe7";
try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "pixel.ppm", .data = bytes });
var directory_buffer: [4096]u8 = undefined;
const directory_len = try tmp.dir.realPath(std.testing.io, &directory_buffer);
const path = try std.fmt.allocPrint(gpa, "/n/os{s}/pixel.ppm", .{directory_buffer[0..directory_len]});
defer gpa.free(path);
const p = try pardes.Pardes.init(gpa, .{ .tty_only = true, .file = path });
defer p.deinit();
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
_ = try p.render(arena.allocator());
const pane = p.panes[0].?;
const state = pane.image.?;
try std.testing.expectEqual(@as(usize, 1), state.iw);
try std.testing.expectEqual(@as(usize, 1), state.ih);
const saved = try pardes.panes.Image.dumpPane(p, arena.allocator(), pane, "", "", 0, path, &.{});
const decoded = try pardes.dump.decodeBytes(gpa, saved.image.?.bytes_b64);
defer gpa.free(decoded);
try std.testing.expectEqualStrings(bytes, decoded);
}
|