milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
This commit is contained in:
@@ -48,6 +48,50 @@ pub fn parseLine(format: Format, line: []const u8) Line {
|
||||
};
|
||||
}
|
||||
|
||||
pub const LineEvent = union(enum) {
|
||||
/// One line without its delimiter, borrowed from the reader's buffer and
|
||||
/// valid only until the next call. A trailing '\r' is left on: whether it
|
||||
/// belongs to the line is the caller's decision.
|
||||
line: []const u8,
|
||||
/// A line longer than `max_len`. It has already been stepped over.
|
||||
long_line,
|
||||
};
|
||||
|
||||
/// One line, or `null` at end of stream. `max_len` bounds a line; anything
|
||||
/// longer comes back as `.long_line` with the stream positioned on the line
|
||||
/// after it, so a caller that keeps calling always advances.
|
||||
///
|
||||
/// The bound is a parameter because this file may not import `compiler.zig`:
|
||||
/// the compiler imports this one, and this file is the root of a separate fuzz
|
||||
/// module. Both callers pass `compiler.max_line_len`.
|
||||
///
|
||||
/// The two over-long paths exist because a `Reader` reports an over-long line
|
||||
/// two different ways. A reader whose buffer is smaller than `max_len` reports
|
||||
/// `error.StreamTooLong` and — this is the hazard — leaves the stream
|
||||
/// unmodified (Reader.zig:895-919), so without the discard a caller re-reads
|
||||
/// the same bytes forever. A reader whose buffer is larger hands the whole line
|
||||
/// over and the length check catches it.
|
||||
///
|
||||
/// An over-long final line with no delimiter ends the stream inside the
|
||||
/// discard. That still counts as a line, so it comes back as `.long_line`; the
|
||||
/// discard drained the stream (Reader.zig:1042), so the next call returns
|
||||
/// `null`.
|
||||
pub fn nextBoundedLine(r: *std.Io.Reader, max_len: usize) error{ReadFailed}!?LineEvent {
|
||||
const raw = r.takeDelimiter('\n') catch |err| switch (err) {
|
||||
error.ReadFailed => return error.ReadFailed,
|
||||
error.StreamTooLong => {
|
||||
_ = r.discardDelimiterInclusive('\n') catch |discard_err| switch (discard_err) {
|
||||
error.EndOfStream => return .long_line,
|
||||
error.ReadFailed => return error.ReadFailed,
|
||||
};
|
||||
return .long_line;
|
||||
},
|
||||
} orelse return null;
|
||||
|
||||
if (raw.len > max_len) return .long_line;
|
||||
return .{ .line = raw };
|
||||
}
|
||||
|
||||
pub const sample_lines = 64;
|
||||
|
||||
/// Picks a format from the first `sample_lines` lines that are not blank and
|
||||
@@ -309,6 +353,58 @@ test "parseLine dispatches to the abp parser" {
|
||||
try testing.expect(line.covers_apex);
|
||||
}
|
||||
|
||||
fn expectLine(expected: []const u8, event: ?LineEvent) !void {
|
||||
const got = event orelse return error.TestExpectedLine;
|
||||
switch (got) {
|
||||
.line => |line| try testing.expectEqualStrings(expected, line),
|
||||
.long_line => return error.TestExpectedLine,
|
||||
}
|
||||
}
|
||||
|
||||
test "nextBoundedLine walks lines and ends at the stream" {
|
||||
var r: std.Io.Reader = .fixed("a\nbb\n\nccc");
|
||||
try expectLine("a", try nextBoundedLine(&r, 16));
|
||||
try expectLine("bb", try nextBoundedLine(&r, 16));
|
||||
try expectLine("", try nextBoundedLine(&r, 16));
|
||||
// A final line with no delimiter is still a line.
|
||||
try expectLine("ccc", try nextBoundedLine(&r, 16));
|
||||
try testing.expectEqual(@as(?LineEvent, null), try nextBoundedLine(&r, 16));
|
||||
}
|
||||
|
||||
test "nextBoundedLine reports an over-long line when the reader buffer is large" {
|
||||
var r: std.Io.Reader = .fixed("a\nxxxxxxxx\nb\n");
|
||||
try expectLine("a", try nextBoundedLine(&r, 4));
|
||||
try testing.expectEqual(LineEvent.long_line, (try nextBoundedLine(&r, 4)).?);
|
||||
try expectLine("b", try nextBoundedLine(&r, 4));
|
||||
try testing.expectEqual(@as(?LineEvent, null), try nextBoundedLine(&r, 4));
|
||||
}
|
||||
|
||||
test "nextBoundedLine steps over a line that does not fit the reader buffer" {
|
||||
// A buffer smaller than the long line makes `takeDelimiter` report
|
||||
// `error.StreamTooLong` and leave the stream where it was, which is the
|
||||
// path that loops forever without the discard.
|
||||
var backing: std.Io.Reader = .fixed("a\n" ++ "x" ** 64 ++ "\nb\n");
|
||||
var buf: [16]u8 = undefined;
|
||||
var limited = backing.limited(.unlimited, &buf);
|
||||
const r = &limited.interface;
|
||||
|
||||
try expectLine("a", try nextBoundedLine(r, 16));
|
||||
try testing.expectEqual(LineEvent.long_line, (try nextBoundedLine(r, 16)).?);
|
||||
try expectLine("b", try nextBoundedLine(r, 16));
|
||||
try testing.expectEqual(@as(?LineEvent, null), try nextBoundedLine(r, 16));
|
||||
}
|
||||
|
||||
test "nextBoundedLine reports an over-long final line that ends inside the discard" {
|
||||
var backing: std.Io.Reader = .fixed("a\n" ++ "x" ** 64);
|
||||
var buf: [16]u8 = undefined;
|
||||
var limited = backing.limited(.unlimited, &buf);
|
||||
const r = &limited.interface;
|
||||
|
||||
try expectLine("a", try nextBoundedLine(r, 16));
|
||||
try testing.expectEqual(LineEvent.long_line, (try nextBoundedLine(r, 16)).?);
|
||||
try testing.expectEqual(@as(?LineEvent, null), try nextBoundedLine(r, 16));
|
||||
}
|
||||
|
||||
test "looksLikeIpLiteral separates addresses from names" {
|
||||
try testing.expect(looksLikeIpLiteral("0.0.0.0"));
|
||||
try testing.expect(looksLikeIpLiteral("127.0.0.1"));
|
||||
|
||||
Reference in New Issue
Block a user