summaryrefslogtreecommitdiff
path: root/src/ninep
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep')
-rw-r--r--src/ninep/ctl.zig15
-rw-r--r--src/ninep/events.zig49
2 files changed, 45 insertions, 19 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 76f09084..d1bb5907 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -1406,3 +1406,18 @@ test "Shell refuses a path that is no executable, and bare it goes back to the d
var want: [300]u8 = undefined;
try testing.expect(std.mem.indexOf(u8, rd(p, root_ctl, 0, 8192).bytes, try std.fmt.bufPrint(&want, "Shell {s}\n", .{config.defaultShell()})) != null);
}
+
+test "a builtin that fails a ctl write logs only its err, so the same failure again is counted" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ p.update(.tick); // the pane's `new` is logged first
+ while (p.nextEffect()) |_| {}
+ const ctl_node = @intFromEnum(tree.TopFile.ctl);
+ for (0..3) |_| try testing.expectEqual(Status.err, wr(p, ctl_node, "Kill zzz\n").reply.status);
+ const log = @intFromEnum(tree.TopFile.log);
+ const h = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle;
+ const text = call(p, .{ .tag = 2, .op = .read, .node = log, .handle = h, .size = 1 << 16 }).bytes;
+ try testing.expect(std.mem.endsWith(u8, text, "err - ctl: Kill: no running command has that first word \"Kill zzz\" (x3)\n"));
+ try testing.expect(std.mem.indexOf(u8, text, "msg - Kill") == null);
+ _ = call(p, .{ .tag = 3, .op = .release, .node = log, .handle = h });
+}
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index c54d8454..e0e66800 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -154,16 +154,7 @@ pub fn noteMessage(p: *Pardes, serial: u32, text: []const u8) void {
// A builtin's own word for what it did stands in for its Verbose
// announcement just before (`Undo` then `Undo: nothing to undo`), so
// that 1000 Undos with nothing left are one line, counted, as err's are.
- if (newest(p)) |last| if (!followerRead(p, last.seq) and std.mem.startsWith(u8, last.text, "msg ")) {
- const rest = last.text["msg ".len..];
- const sp = std.mem.indexOfScalar(u8, rest, ' ') orelse rest.len;
- const word = rest[@min(sp + 1, rest.len)..];
- var who: [16]u8 = undefined;
- const mine = if (serial == 0) "-" else std.fmt.bufPrint(&who, "{d}", .{serial}) catch "";
- if (std.mem.eql(u8, rest[0..sp], mine) and word.len > 0 and std.mem.indexOfScalar(u8, word, ' ') == null and
- text.len > word.len and std.mem.startsWith(u8, text, word) and text[word.len] == ':')
- p.fs.log.buf.shrinkRetainingCapacity(last.at);
- };
+ dropAnnouncement(p, serial, text);
pushCounting(p, (if (serial == 0)
std.fmt.bufPrint(&buf, "msg - {s}\n", .{text})
else
@@ -218,8 +209,27 @@ pub fn noteError(p: *Pardes, req: Req, reply: Reply) void {
pushCounting(p, record);
}
+/// Drops the newest record when it is the Verbose announcement (`msg
+/// <serial> <Word>`) of the builtin whose words `text` are (`Word: ...`):
+/// what the builtin said, or the err its failure logs, takes its place.
+/// Not one a follower has read.
+pub fn dropAnnouncement(p: *Pardes, serial: u32, text: []const u8) void {
+ const last = newest(p) orelse return;
+ if (followerRead(p, last.seq) or !std.mem.startsWith(u8, last.text, "msg ")) return;
+ const rest = last.text["msg ".len..];
+ const sp = std.mem.indexOfScalar(u8, rest, ' ') orelse rest.len;
+ const word = rest[@min(sp + 1, rest.len)..];
+ var who: [16]u8 = undefined;
+ const mine = if (serial == 0) "-" else std.fmt.bufPrint(&who, "{d}", .{serial}) catch "";
+ if (std.mem.eql(u8, rest[0..sp], mine) and word.len > 0 and std.mem.indexOfScalar(u8, word, ' ') == null and
+ text.len > word.len and std.mem.startsWith(u8, text, word) and text[word.len] == ':')
+ p.fs.log.buf.shrinkRetainingCapacity(last.at);
+}
+
/// Pushes `record`, or, when it repeats the newest record word for word,
-/// counts that one instead: `<record> (x2)`.
+/// counts that one instead: `<record> (x2)`, the count being every time it
+/// was said. One a follower has already read is not rewritten: the repeat
+/// is a new line carrying the running count, `(x3)`.
fn pushCounting(p: *Pardes, record: []u8) void {
// A client retrying a write that fails the same way would fill the ring
// with one line, so a repeat of the newest record is that record counted,
@@ -234,10 +244,10 @@ fn pushCounting(p: *Pardes, record: []u8) void {
said = said[0..x];
} else |_| {}
};
- if (!std.mem.eql(u8, said, record[0 .. record.len - 1]) or followerRead(p, last.seq)) return pushLog(p, record);
+ if (!std.mem.eql(u8, said, record[0 .. record.len - 1])) return pushLog(p, record);
var counted: [pardes.Messages.LoggedMessage.cap + 64]u8 = undefined;
const again = std.fmt.bufPrint(&counted, "{s} (x{d})\n", .{ said, times +| 1 }) catch return;
- p.fs.log.buf.shrinkRetainingCapacity(last.at);
+ if (!followerRead(p, last.seq)) p.fs.log.buf.shrinkRetainingCapacity(last.at);
pushLog(p, again);
}
@@ -900,7 +910,7 @@ test "the log records whether or not anyone reads, and an open that follows wait
}
try testing.expectEqualStrings(try std.fmt.bufPrint(&expected, "msg {d} same failure\n", .{said_by}), pieced[0..got]);
p.setMessage(0, "same failure");
- try testing.expectEqualStrings(try std.fmt.bufPrint(&expected, "msg {d} same failure\n", .{said_by}), rdf.next(p, log, fh, frozen).bytes);
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&expected, "msg {d} same failure (x2)\n", .{said_by}), rdf.next(p, log, fh, frozen).bytes);
// A follower the ring outran hears how much it missed, then carries on.
var filler: [200]u8 = @splat('x');
@@ -951,19 +961,20 @@ test "a refused or failed write is an err record in the log, saying which file a
// A write that succeeds says nothing.
_ = wr(p, Node.of(serial, .addr), "#0");
try testing.expectEqual(Status.again, call(p, .{ .tag = 5, .op = .read, .node = log, .handle = f, .off = frozen, .size = 4096 }).reply.status);
- // A follower that has read an err waits on its repeat: a line of its own.
+ // A follower that has read an err waits on its repeat: a line of its
+ // own, with the running count.
_ = wr(p, Node.of(serial, .addr), "/nowhere/");
try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "err {d} addr: no match for regexp\n", .{serial}), next.record(p, f, frozen));
_ = wr(p, Node.of(serial, .addr), "/nowhere/");
- try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "err {d} addr: no match for regexp\n", .{serial}), next.record(p, f, frozen));
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "err {d} addr: no match for regexp (x2)\n", .{serial}), next.record(p, f, frozen));
_ = call(p, .{ .tag = 6, .op = .release, .node = log, .handle = f });
- // With the follower gone, repeats are the newest record counted: the
- // one it read, and three more.
+ // With the follower gone, repeats are the newest record counted on:
+ // its two, and three more.
for (0..3) |_| _ = wr(p, Node.of(serial, .addr), "/elsewhere/");
const g = call(p, .{ .tag = 7, .op = .open, .node = log }).reply.handle;
const text = call(p, .{ .tag = 8, .op = .read, .node = log, .handle = g, .size = 1 << 16 }).bytes;
- try testing.expect(std.mem.endsWith(u8, text, try std.fmt.bufPrint(&want, "regexp\nerr {d} addr: no match for regexp (x4)\n", .{serial})));
+ try testing.expect(std.mem.endsWith(u8, text, try std.fmt.bufPrint(&want, "regexp\nerr {d} addr: no match for regexp (x5)\n", .{serial})));
_ = call(p, .{ .tag = 9, .op = .release, .node = log, .handle = g });
}