aboutsummaryrefslogtreecommitdiff
path: root/lib/std/testing.zig
diff options
context:
space:
mode:
authorRyan Liptak <squeek502@hotmail.com>2025-10-03 01:18:53 -0700
committerAndrew Kelley <andrew@ziglang.org>2025-10-08 16:42:55 -0700
commit328ae41468f3514251ac1b0726c41eca9fbc3fb5 (patch)
tree32315d9cc319b31a88afb68b0d5b7448b64cbf2d /lib/std/testing.zig
parent60be67d3c0ba6ae15fa7115596734ab1e74fbcd3 (diff)
downloadzig-328ae41468f3514251ac1b0726c41eca9fbc3fb5.tar.gz
zig-328ae41468f3514251ac1b0726c41eca9fbc3fb5.zip
Reader.peekDelimiterInclusive: Fix handling of `stream` implementations that return 0
Previously, the logic in peekDelimiterInclusive (when the delimiter was not found in the existing buffer) used the `n` returned from `r.vtable.stream` as the length of the slice to check, but it's valid for `vtable.stream` implementations to return 0 if they wrote to the buffer instead of `w`. In that scenario, the `indexOfScalarPos` would be given a 0-length slice so it would never be able to find the delimiter. This commit changes the logic to assume that `r.vtable.stream` can both: - return 0, and - modify seek/end (i.e. it's also valid for a `vtable.stream` implementation to rebase) Also introduces `std.testing.ReaderIndirect` which helps in being able to test against Reader implementations that return 0 from `stream`/`readVec` Fixes #25428
Diffstat (limited to 'lib/std/testing.zig')
-rw-r--r--lib/std/testing.zig60
1 files changed, 60 insertions, 0 deletions
diff --git a/lib/std/testing.zig b/lib/std/testing.zig
index 1c6c639796..e1e78c3bec 100644
--- a/lib/std/testing.zig
+++ b/lib/std/testing.zig
@@ -1242,3 +1242,63 @@ pub const Reader = struct {
return n;
}
};
+
+/// A `std.Io.Reader` that gets its data from another `std.Io.Reader`, and always
+/// writes to its own buffer (and returns 0) during `stream` and `readVec`.
+pub const ReaderIndirect = struct {
+ in: *std.Io.Reader,
+ interface: std.Io.Reader,
+
+ pub fn init(in: *std.Io.Reader, buffer: []u8) ReaderIndirect {
+ return .{
+ .in = in,
+ .interface = .{
+ .vtable = &.{
+ .stream = stream,
+ .readVec = readVec,
+ },
+ .buffer = buffer,
+ .seek = 0,
+ .end = 0,
+ },
+ };
+ }
+
+ fn readVec(r: *std.Io.Reader, _: [][]u8) std.Io.Reader.Error!usize {
+ try streamInner(r);
+ return 0;
+ }
+
+ fn stream(r: *std.Io.Reader, _: *std.Io.Writer, _: std.Io.Limit) std.Io.Reader.StreamError!usize {
+ try streamInner(r);
+ return 0;
+ }
+
+ fn streamInner(r: *std.Io.Reader) std.Io.Reader.Error!void {
+ const r_indirect: *ReaderIndirect = @alignCast(@fieldParentPtr("interface", r));
+
+ // If there's no room remaining in the buffer at all, make room.
+ if (r.buffer.len == r.end) {
+ try r.rebase(r.buffer.len);
+ }
+
+ var writer: std.Io.Writer = .{
+ .buffer = r.buffer,
+ .end = r.end,
+ .vtable = &.{
+ .drain = std.Io.Writer.unreachableDrain,
+ .rebase = std.Io.Writer.unreachableRebase,
+ },
+ };
+ defer r.end = writer.end;
+
+ r_indirect.in.streamExact(&writer, r.buffer.len - r.end) catch |err| switch (err) {
+ // Only forward EndOfStream if no new bytes were written to the buffer
+ error.EndOfStream => |e| if (r.end == writer.end) {
+ return e;
+ },
+ error.WriteFailed => unreachable,
+ else => |e| return e,
+ };
+ }
+};