diff options
| author | Ryan Liptak <squeek502@hotmail.com> | 2025-10-03 01:18:53 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2025-10-08 16:42:55 -0700 |
| commit | 328ae41468f3514251ac1b0726c41eca9fbc3fb5 (patch) | |
| tree | 32315d9cc319b31a88afb68b0d5b7448b64cbf2d /lib/std/testing.zig | |
| parent | 60be67d3c0ba6ae15fa7115596734ab1e74fbcd3 (diff) | |
| download | zig-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.zig | 60 |
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, + }; + } +}; |
