diff options
Diffstat (limited to 'src/ninep')
| -rw-r--r-- | src/ninep/ctl.zig | 15 | ||||
| -rw-r--r-- | src/ninep/events.zig | 49 |
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 }); } |
