| ... | @@ -4,7 +4,7 @@ const native_endian = builtin.target.cpu.arch.endian(); | ... | @@ -4,7 +4,7 @@ const native_endian = builtin.target.cpu.arch.endian(); |
| 4 | const Writer = @This(); | 4 | const Writer = @This(); |
| 5 | const std = @import("../std.zig"); | 5 | const std = @import("../std.zig"); |
| 6 | const assert = std.debug.assert; | 6 | const assert = std.debug.assert; |
| 7 | const Limit = std.io.Limit; | 7 | const Limit = std.Io.Limit; |
| 8 | const File = std.fs.File; | 8 | const File = std.fs.File; |
| 9 | const testing = std.testing; | 9 | const testing = std.testing; |
| 10 | const Allocator = std.mem.Allocator; | 10 | const Allocator = std.mem.Allocator; |
| ... | @@ -76,6 +76,14 @@ pub const VTable = struct { | ... | @@ -76,6 +76,14 @@ pub const VTable = struct { |
| 76 | /// There may be subsequent calls to `drain` and `sendFile` after a `flush` | 76 | /// There may be subsequent calls to `drain` and `sendFile` after a `flush` |
| 77 | /// operation. | 77 | /// operation. |
| 78 | flush: *const fn (w: *Writer) Error!void = defaultFlush, | 78 | flush: *const fn (w: *Writer) Error!void = defaultFlush, |
| | 79 | |
| | 80 | /// Ensures `capacity` more bytes can be buffered without rebasing. |
| | 81 | /// |
| | 82 | /// The most recent `preserve` bytes must remain buffered. |
| | 83 | /// |
| | 84 | /// Only called when `capacity` bytes cannot fit into the unused capacity |
| | 85 | /// of `buffer`. |
| | 86 | rebase: *const fn (w: *Writer, preserve: usize, capacity: usize) Error!void = defaultRebase, |
| 79 | }; | 87 | }; |
| 80 | | 88 | |
| 81 | pub const Error = error{ | 89 | pub const Error = error{ |
| ... | @@ -117,6 +125,7 @@ pub fn fixed(buffer: []u8) Writer { | ... | @@ -117,6 +125,7 @@ pub fn fixed(buffer: []u8) Writer { |
| 117 | .vtable = &.{ | 125 | .vtable = &.{ |
| 118 | .drain = fixedDrain, | 126 | .drain = fixedDrain, |
| 119 | .flush = noopFlush, | 127 | .flush = noopFlush, |
| | 128 | .rebase = failingRebase, |
| 120 | }, | 129 | }, |
| 121 | .buffer = buffer, | 130 | .buffer = buffer, |
| 122 | }; | 131 | }; |
| ... | @@ -130,6 +139,7 @@ pub const failing: Writer = .{ | ... | @@ -130,6 +139,7 @@ pub const failing: Writer = .{ |
| 130 | .vtable = &.{ | 139 | .vtable = &.{ |
| 131 | .drain = failingDrain, | 140 | .drain = failingDrain, |
| 132 | .sendFile = failingSendFile, | 141 | .sendFile = failingSendFile, |
| | 142 | .rebase = failingRebase, |
| 133 | }, | 143 | }, |
| 134 | }; | 144 | }; |
| 135 | | 145 | |
| ... | @@ -276,7 +286,7 @@ fn writeSplatHeaderLimitFinish( | ... | @@ -276,7 +286,7 @@ fn writeSplatHeaderLimitFinish( |
| 276 | | 286 | |
| 277 | test "writeSplatHeader splatting avoids buffer aliasing temptation" { | 287 | test "writeSplatHeader splatting avoids buffer aliasing temptation" { |
| 278 | const initial_buf = try testing.allocator.alloc(u8, 8); | 288 | const initial_buf = try testing.allocator.alloc(u8, 8); |
| 279 | var aw: std.io.Writer.Allocating = .initOwnedSlice(testing.allocator, initial_buf); | 289 | var aw: Allocating = .initOwnedSlice(testing.allocator, initial_buf); |
| 280 | defer aw.deinit(); | 290 | defer aw.deinit(); |
| 281 | // This test assumes 8 vector buffer in this function. | 291 | // This test assumes 8 vector buffer in this function. |
| 282 | const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{ | 292 | const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{ |
| ... | @@ -307,24 +317,41 @@ pub fn noopFlush(w: *Writer) Error!void { | ... | @@ -307,24 +317,41 @@ pub fn noopFlush(w: *Writer) Error!void { |
| 307 | | 317 | |
| 308 | test "fixed buffer flush" { | 318 | test "fixed buffer flush" { |
| 309 | var buffer: [1]u8 = undefined; | 319 | var buffer: [1]u8 = undefined; |
| 310 | var writer: std.io.Writer = .fixed(&buffer); | 320 | var writer: Writer = .fixed(&buffer); |
| 311 | | 321 | |
| 312 | try writer.writeByte(10); | 322 | try writer.writeByte(10); |
| 313 | try writer.flush(); | 323 | try writer.flush(); |
| 314 | try testing.expectEqual(10, buffer[0]); | 324 | try testing.expectEqual(10, buffer[0]); |
| 315 | } | 325 | } |
| 316 | | 326 | |
| 317 | /// Calls `VTable.drain` but hides the last `preserve_len` bytes from the | 327 | pub fn rebase(w: *Writer, preserve: usize, unused_capacity_len: usize) Error!void { |
| 318 | /// implementation, keeping them buffered. | 328 | if (w.buffer.len - w.end >= unused_capacity_len) { |
| 319 | pub fn drainPreserve(w: *Writer, preserve_len: usize) Error!void { | 329 | @branchHint(.likely); |
| 320 | const preserved_head = w.end -| preserve_len; | 330 | return; |
| 321 | const preserved_tail = w.end; | 331 | } |
| 322 | const preserved_len = preserved_tail - preserved_head; | 332 | try w.vtable.rebase(w, preserve, unused_capacity_len); |
| 323 | w.end = preserved_head; | 333 | } |
| 324 | defer w.end += preserved_len; | 334 | |
| 325 | assert(0 == try w.vtable.drain(w, &.{""}, 1)); | 335 | pub fn defaultRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void { |
| 326 | assert(w.end <= preserved_head + preserved_len); | 336 | while (w.buffer.len - w.end < minimum_len) { |
| 327 | @memmove(w.buffer[w.end..][0..preserved_len], w.buffer[preserved_head..preserved_tail]); | 337 | { |
| | 338 | // TODO: instead of this logic that "hides" data from |
| | 339 | // the implementation, introduce a seek index to Writer |
| | 340 | const preserved_head = w.end -| preserve; |
| | 341 | const preserved_tail = w.end; |
| | 342 | const preserved_len = preserved_tail - preserved_head; |
| | 343 | w.end = preserved_head; |
| | 344 | defer w.end += preserved_len; |
| | 345 | assert(0 == try w.vtable.drain(w, &.{""}, 1)); |
| | 346 | assert(w.end <= preserved_head + preserved_len); |
| | 347 | @memmove(w.buffer[w.end..][0..preserved_len], w.buffer[preserved_head..preserved_tail]); |
| | 348 | } |
| | 349 | |
| | 350 | // If the loop condition was false this assertion would have passed |
| | 351 | // anyway. Otherwise, give the implementation a chance to grow the |
| | 352 | // buffer before asserting on the buffer length. |
| | 353 | assert(w.buffer.len - preserve >= minimum_len); |
| | 354 | } |
| 328 | } | 355 | } |
| 329 | | 356 | |
| 330 | pub fn unusedCapacitySlice(w: *const Writer) []u8 { | 357 | pub fn unusedCapacitySlice(w: *const Writer) []u8 { |
| ... | @@ -353,53 +380,44 @@ pub fn writableSlice(w: *Writer, len: usize) Error![]u8 { | ... | @@ -353,53 +380,44 @@ pub fn writableSlice(w: *Writer, len: usize) Error![]u8 { |
| 353 | return big_slice[0..len]; | 380 | return big_slice[0..len]; |
| 354 | } | 381 | } |
| 355 | | 382 | |
| 356 | /// Asserts the provided buffer has total capacity enough for `minimum_length`. | 383 | /// Asserts the provided buffer has total capacity enough for `minimum_len`. |
| 357 | /// | 384 | /// |
| 358 | /// Does not `advance` the buffer end position. | 385 | /// Does not `advance` the buffer end position. |
| 359 | /// | 386 | /// |
| 360 | /// If `minimum_length` is zero, this is equivalent to `unusedCapacitySlice`. | 387 | /// If `minimum_len` is zero, this is equivalent to `unusedCapacitySlice`. |
| 361 | pub fn writableSliceGreedy(w: *Writer, minimum_length: usize) Error![]u8 { | 388 | pub fn writableSliceGreedy(w: *Writer, minimum_len: usize) Error![]u8 { |
| 362 | while (w.buffer.len - w.end < minimum_length) { | 389 | return writableSliceGreedyPreserve(w, 0, minimum_len); |
| 363 | assert(0 == try w.vtable.drain(w, &.{""}, 1)); | | |
| 364 | // If the loop condition was false this assertion would have passed | | |
| 365 | // anyway. Otherwise, give the implementation a chance to grow the | | |
| 366 | // buffer before asserting on the buffer length. | | |
| 367 | assert(w.buffer.len >= minimum_length); | | |
| 368 | } else { | | |
| 369 | @branchHint(.likely); | | |
| 370 | return w.buffer[w.end..]; | | |
| 371 | } | | |
| 372 | } | 390 | } |
| 373 | | 391 | |
| 374 | /// Asserts the provided buffer has total capacity enough for `minimum_length` | 392 | /// Asserts the provided buffer has total capacity enough for `minimum_len` |
| 375 | /// and `preserve_len` combined. | 393 | /// and `preserve` combined. |
| 376 | /// | 394 | /// |
| 377 | /// Does not `advance` the buffer end position. | 395 | /// Does not `advance` the buffer end position. |
| 378 | /// | 396 | /// |
| 379 | /// When draining the buffer, ensures that at least `preserve_len` bytes | 397 | /// When draining the buffer, ensures that at least `preserve` bytes |
| 380 | /// remain buffered. | 398 | /// remain buffered. |
| 381 | /// | 399 | /// |
| 382 | /// If `preserve_len` is zero, this is equivalent to `writableSliceGreedy`. | 400 | /// If `preserve` is zero, this is equivalent to `writableSliceGreedy`. |
| 383 | pub fn writableSliceGreedyPreserve(w: *Writer, preserve_len: usize, minimum_length: usize) Error![]u8 { | 401 | pub fn writableSliceGreedyPreserve(w: *Writer, preserve: usize, minimum_len: usize) Error![]u8 { |
| 384 | assert(w.buffer.len >= preserve_len + minimum_length); | 402 | if (w.buffer.len - w.end >= minimum_len) { |
| 385 | while (w.buffer.len - w.end < minimum_length) { | | |
| 386 | try drainPreserve(w, preserve_len); | | |
| 387 | } else { | | |
| 388 | @branchHint(.likely); | 403 | @branchHint(.likely); |
| 389 | return w.buffer[w.end..]; | 404 | return w.buffer[w.end..]; |
| 390 | } | 405 | } |
| | 406 | try rebase(w, preserve, minimum_len); |
| | 407 | assert(w.buffer.len >= preserve + minimum_len); |
| | 408 | return w.buffer[w.end..]; |
| 391 | } | 409 | } |
| 392 | | 410 | |
| 393 | /// Asserts the provided buffer has total capacity enough for `len`. | 411 | /// Asserts the provided buffer has total capacity enough for `len`. |
| 394 | /// | 412 | /// |
| 395 | /// Advances the buffer end position by `len`. | 413 | /// Advances the buffer end position by `len`. |
| 396 | /// | 414 | /// |
| 397 | /// When draining the buffer, ensures that at least `preserve_len` bytes | 415 | /// When draining the buffer, ensures that at least `preserve` bytes |
| 398 | /// remain buffered. | 416 | /// remain buffered. |
| 399 | /// | 417 | /// |
| 400 | /// If `preserve_len` is zero, this is equivalent to `writableSlice`. | 418 | /// If `preserve` is zero, this is equivalent to `writableSlice`. |
| 401 | pub fn writableSlicePreserve(w: *Writer, preserve_len: usize, len: usize) Error![]u8 { | 419 | pub fn writableSlicePreserve(w: *Writer, preserve: usize, len: usize) Error![]u8 { |
| 402 | const big_slice = try w.writableSliceGreedyPreserve(preserve_len, len); | 420 | const big_slice = try w.writableSliceGreedyPreserve(preserve, len); |
| 403 | advance(w, len); | 421 | advance(w, len); |
| 404 | return big_slice[0..len]; | 422 | return big_slice[0..len]; |
| 405 | } | 423 | } |
| ... | @@ -708,16 +726,18 @@ pub fn writeByte(w: *Writer, byte: u8) Error!void { | ... | @@ -708,16 +726,18 @@ pub fn writeByte(w: *Writer, byte: u8) Error!void { |
| 708 | } | 726 | } |
| 709 | } | 727 | } |
| 710 | | 728 | |
| 711 | /// When draining the buffer, ensures that at least `preserve_len` bytes | 729 | /// When draining the buffer, ensures that at least `preserve` bytes |
| 712 | /// remain buffered. | 730 | /// remain buffered. |
| 713 | pub fn writeBytePreserve(w: *Writer, preserve_len: usize, byte: u8) Error!void { | 731 | pub fn writeBytePreserve(w: *Writer, preserve: usize, byte: u8) Error!void { |
| 714 | while (w.buffer.len - w.end == 0) { | 732 | if (w.buffer.len - w.end != 0) { |
| 715 | try drainPreserve(w, preserve_len); | | |
| 716 | } else { | | |
| 717 | @branchHint(.likely); | 733 | @branchHint(.likely); |
| 718 | w.buffer[w.end] = byte; | 734 | w.buffer[w.end] = byte; |
| 719 | w.end += 1; | 735 | w.end += 1; |
| | 736 | return; |
| 720 | } | 737 | } |
| | 738 | try w.vtable.rebase(w, preserve, 1); |
| | 739 | w.buffer[w.end] = byte; |
| | 740 | w.end += 1; |
| 721 | } | 741 | } |
| 722 | | 742 | |
| 723 | /// Writes the same byte many times, performing the underlying write call as | 743 | /// Writes the same byte many times, performing the underlying write call as |
| ... | @@ -735,18 +755,18 @@ test splatByteAll { | ... | @@ -735,18 +755,18 @@ test splatByteAll { |
| 735 | try testing.expectEqualStrings("7" ** 45, aw.writer.buffered()); | 755 | try testing.expectEqualStrings("7" ** 45, aw.writer.buffered()); |
| 736 | } | 756 | } |
| 737 | | 757 | |
| 738 | pub fn splatBytePreserve(w: *Writer, preserve_len: usize, byte: u8, n: usize) Error!void { | 758 | pub fn splatBytePreserve(w: *Writer, preserve: usize, byte: u8, n: usize) Error!void { |
| 739 | const new_end = w.end + n; | 759 | const new_end = w.end + n; |
| 740 | if (new_end <= w.buffer.len) { | 760 | if (new_end <= w.buffer.len) { |
| 741 | @memset(w.buffer[w.end..][0..n], byte); | 761 | @memset(w.buffer[w.end..][0..n], byte); |
| 742 | w.end = new_end; | 762 | w.end = new_end; |
| 743 | return; | 763 | return; |
| 744 | } | 764 | } |
| 745 | // If `n` is large, we can ignore `preserve_len` up to a point. | 765 | // If `n` is large, we can ignore `preserve` up to a point. |
| 746 | var remaining = n; | 766 | var remaining = n; |
| 747 | while (remaining > preserve_len) { | 767 | while (remaining > preserve) { |
| 748 | assert(remaining != 0); | 768 | assert(remaining != 0); |
| 749 | remaining -= try splatByte(w, byte, remaining - preserve_len); | 769 | remaining -= try splatByte(w, byte, remaining - preserve); |
| 750 | if (w.end + remaining <= w.buffer.len) { | 770 | if (w.end + remaining <= w.buffer.len) { |
| 751 | @memset(w.buffer[w.end..][0..remaining], byte); | 771 | @memset(w.buffer[w.end..][0..remaining], byte); |
| 752 | w.end += remaining; | 772 | w.end += remaining; |
| ... | @@ -754,9 +774,9 @@ pub fn splatBytePreserve(w: *Writer, preserve_len: usize, byte: u8, n: usize) Er | ... | @@ -754,9 +774,9 @@ pub fn splatBytePreserve(w: *Writer, preserve_len: usize, byte: u8, n: usize) Er |
| 754 | } | 774 | } |
| 755 | } | 775 | } |
| 756 | // All the next bytes received must be preserved. | 776 | // All the next bytes received must be preserved. |
| 757 | if (preserve_len < w.end) { | 777 | if (preserve < w.end) { |
| 758 | @memmove(w.buffer[0..preserve_len], w.buffer[w.end - preserve_len ..][0..preserve_len]); | 778 | @memmove(w.buffer[0..preserve], w.buffer[w.end - preserve ..][0..preserve]); |
| 759 | w.end = preserve_len; | 779 | w.end = preserve; |
| 760 | } | 780 | } |
| 761 | while (remaining > 0) remaining -= try w.splatByte(byte, remaining); | 781 | while (remaining > 0) remaining -= try w.splatByte(byte, remaining); |
| 762 | } | 782 | } |
| ... | @@ -1667,7 +1687,7 @@ pub const ByteSizeUnits = enum { | ... | @@ -1667,7 +1687,7 @@ pub const ByteSizeUnits = enum { |
| 1667 | | 1687 | |
| 1668 | /// Format option `precision` is ignored when `value` is less than 1kB | 1688 | /// Format option `precision` is ignored when `value` is less than 1kB |
| 1669 | pub fn printByteSize( | 1689 | pub fn printByteSize( |
| 1670 | w: *std.io.Writer, | 1690 | w: *Writer, |
| 1671 | value: u64, | 1691 | value: u64, |
| 1672 | comptime units: ByteSizeUnits, | 1692 | comptime units: ByteSizeUnits, |
| 1673 | options: std.fmt.Options, | 1693 | options: std.fmt.Options, |
| ... | @@ -2169,7 +2189,7 @@ test "fixed output" { | ... | @@ -2169,7 +2189,7 @@ test "fixed output" { |
| 2169 | | 2189 | |
| 2170 | test "writeSplat 0 len splat larger than capacity" { | 2190 | test "writeSplat 0 len splat larger than capacity" { |
| 2171 | var buf: [8]u8 = undefined; | 2191 | var buf: [8]u8 = undefined; |
| 2172 | var w: std.io.Writer = .fixed(&buf); | 2192 | var w: Writer = .fixed(&buf); |
| 2173 | const n = try w.writeSplat(&.{"something that overflows buf"}, 0); | 2193 | const n = try w.writeSplat(&.{"something that overflows buf"}, 0); |
| 2174 | try testing.expectEqual(0, n); | 2194 | try testing.expectEqual(0, n); |
| 2175 | } | 2195 | } |
| ... | @@ -2188,6 +2208,13 @@ pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) File | ... | @@ -2188,6 +2208,13 @@ pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) File |
| 2188 | return error.WriteFailed; | 2208 | return error.WriteFailed; |
| 2189 | } | 2209 | } |
| 2190 | | 2210 | |
| | 2211 | pub fn failingRebase(w: *Writer, preserve: usize, capacity: usize) Error!void { |
| | 2212 | _ = w; |
| | 2213 | _ = preserve; |
| | 2214 | _ = capacity; |
| | 2215 | return error.WriteFailed; |
| | 2216 | } |
| | 2217 | |
| 2191 | pub const Discarding = struct { | 2218 | pub const Discarding = struct { |
| 2192 | count: u64, | 2219 | count: u64, |
| 2193 | writer: Writer, | 2220 | writer: Writer, |
| ... | @@ -2455,7 +2482,7 @@ pub fn Hashing(comptime Hasher: type) type { | ... | @@ -2455,7 +2482,7 @@ pub fn Hashing(comptime Hasher: type) type { |
| 2455 | /// Maintains `Writer` state such that it writes to the unused capacity of an | 2482 | /// Maintains `Writer` state such that it writes to the unused capacity of an |
| 2456 | /// array list, filling it up completely before making a call through the | 2483 | /// array list, filling it up completely before making a call through the |
| 2457 | /// vtable, causing a resize. Consequently, the same, optimized, non-generic | 2484 | /// vtable, causing a resize. Consequently, the same, optimized, non-generic |
| 2458 | /// machine code that uses `std.io.Reader`, such as formatted printing, takes | 2485 | /// machine code that uses `std.Io.Reader`, such as formatted printing, takes |
| 2459 | /// the hot paths when using this API. | 2486 | /// the hot paths when using this API. |
| 2460 | /// | 2487 | /// |
| 2461 | /// When using this API, it is not necessary to call `flush`. | 2488 | /// When using this API, it is not necessary to call `flush`. |
| ... | @@ -2514,6 +2541,7 @@ pub const Allocating = struct { | ... | @@ -2514,6 +2541,7 @@ pub const Allocating = struct { |
| 2514 | .drain = Allocating.drain, | 2541 | .drain = Allocating.drain, |
| 2515 | .sendFile = Allocating.sendFile, | 2542 | .sendFile = Allocating.sendFile, |
| 2516 | .flush = noopFlush, | 2543 | .flush = noopFlush, |
| | 2544 | .rebase = growingRebase, |
| 2517 | }; | 2545 | }; |
| 2518 | | 2546 | |
| 2519 | pub fn deinit(a: *Allocating) void { | 2547 | pub fn deinit(a: *Allocating) void { |
| ... | @@ -2595,7 +2623,7 @@ pub const Allocating = struct { | ... | @@ -2595,7 +2623,7 @@ pub const Allocating = struct { |
| 2595 | return list.items.len - start_len; | 2623 | return list.items.len - start_len; |
| 2596 | } | 2624 | } |
| 2597 | | 2625 | |
| 2598 | fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize { | 2626 | fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { |
| 2599 | if (File.Handle == void) return error.Unimplemented; | 2627 | if (File.Handle == void) return error.Unimplemented; |
| 2600 | if (limit == .nothing) return 0; | 2628 | if (limit == .nothing) return 0; |
| 2601 | const a: *Allocating = @fieldParentPtr("writer", w); | 2629 | const a: *Allocating = @fieldParentPtr("writer", w); |
| ... | @@ -2612,6 +2640,15 @@ pub const Allocating = struct { | ... | @@ -2612,6 +2640,15 @@ pub const Allocating = struct { |
| 2612 | return n; | 2640 | return n; |
| 2613 | } | 2641 | } |
| 2614 | | 2642 | |
| | 2643 | fn growingRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void { |
| | 2644 | _ = preserve; // This implementation always preserves the entire buffer. |
| | 2645 | const a: *Allocating = @fieldParentPtr("writer", w); |
| | 2646 | const gpa = a.allocator; |
| | 2647 | var list = a.toArrayList(); |
| | 2648 | defer setArrayList(a, list); |
| | 2649 | list.ensureUnusedCapacity(gpa, minimum_len) catch return error.WriteFailed; |
| | 2650 | } |
| | 2651 | |
| 2615 | fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void { | 2652 | fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void { |
| 2616 | a.writer.buffer = list.allocatedSlice(); | 2653 | a.writer.buffer = list.allocatedSlice(); |
| 2617 | a.writer.end = list.items.len; | 2654 | a.writer.end = list.items.len; |
| ... | @@ -2645,7 +2682,7 @@ test "discarding sendFile" { | ... | @@ -2645,7 +2682,7 @@ test "discarding sendFile" { |
| 2645 | try file_reader.seekTo(0); | 2682 | try file_reader.seekTo(0); |
| 2646 | | 2683 | |
| 2647 | var w_buffer: [256]u8 = undefined; | 2684 | var w_buffer: [256]u8 = undefined; |
| 2648 | var discarding: std.io.Writer.Discarding = .init(&w_buffer); | 2685 | var discarding: Writer.Discarding = .init(&w_buffer); |
| 2649 | | 2686 | |
| 2650 | _ = try file_reader.interface.streamRemaining(&discarding.writer); | 2687 | _ = try file_reader.interface.streamRemaining(&discarding.writer); |
| 2651 | } | 2688 | } |
| ... | @@ -2664,7 +2701,7 @@ test "allocating sendFile" { | ... | @@ -2664,7 +2701,7 @@ test "allocating sendFile" { |
| 2664 | var file_reader = file_writer.moveToReader(); | 2701 | var file_reader = file_writer.moveToReader(); |
| 2665 | try file_reader.seekTo(0); | 2702 | try file_reader.seekTo(0); |
| 2666 | | 2703 | |
| 2667 | var allocating: std.io.Writer.Allocating = .init(testing.allocator); | 2704 | var allocating: Writer.Allocating = .init(testing.allocator); |
| 2668 | defer allocating.deinit(); | 2705 | defer allocating.deinit(); |
| 2669 | | 2706 | |
| 2670 | _ = try file_reader.interface.streamRemaining(&allocating.writer); | 2707 | _ = try file_reader.interface.streamRemaining(&allocating.writer); |
| ... | @@ -2702,3 +2739,10 @@ test writeSliceEndian { | ... | @@ -2702,3 +2739,10 @@ test writeSliceEndian { |
| 2702 | try writeSliceEndian(&w, u16, &array, .big); | 2739 | try writeSliceEndian(&w, u16, &array, .big); |
| 2703 | try testing.expectEqualSlices(u8, &.{ 'x', 0x12, 0x34, 0x56, 0x78 }, &buffer); | 2740 | try testing.expectEqualSlices(u8, &.{ 'x', 0x12, 0x34, 0x56, 0x78 }, &buffer); |
| 2704 | } | 2741 | } |
| | 2742 | |
| | 2743 | test "writableSlice with fixed writer" { |
| | 2744 | var buf: [2]u8 = undefined; |
| | 2745 | var w: std.Io.Writer = .fixed(&buf); |
| | 2746 | try w.writeByte(1); |
| | 2747 | try std.testing.expectError(error.WriteFailed, w.writableSlice(2)); |
| | 2748 | } |