master
1const std = @import("std.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const math = std.math;
5
6/// Provides deterministic randomness in unit tests.
7/// Initialized on startup. Read-only after that.
8pub var random_seed: u32 = 0;
9
10pub const FailingAllocator = @import("testing/FailingAllocator.zig");
11pub const failing_allocator = failing_allocator_instance.allocator();
12var failing_allocator_instance = FailingAllocator.init(base_allocator_instance.allocator(), .{
13 .fail_index = 0,
14});
15var base_allocator_instance = std.heap.FixedBufferAllocator.init("");
16
17/// This should only be used in temporary test programs.
18pub const allocator = allocator_instance.allocator();
19pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{
20 .stack_trace_frames = if (std.debug.sys_can_stack_trace) 10 else 0,
21 .resize_stack_traces = true,
22 // A unique value so that when a default-constructed
23 // GeneralPurposeAllocator is incorrectly passed to testing allocator, or
24 // vice versa, panic occurs.
25 .canary = @truncate(0x2731e675c3a701ba),
26}) = b: {
27 if (!builtin.is_test) @compileError("testing allocator used when not testing");
28 break :b .init;
29};
30
31pub var io_instance: std.Io.Threaded = undefined;
32pub const io = io_instance.io();
33
34/// TODO https://github.com/ziglang/zig/issues/5738
35pub var log_level = std.log.Level.warn;
36
37// Disable printing in tests for simple backends.
38pub const backend_can_print = switch (builtin.zig_backend) {
39 .stage2_aarch64,
40 .stage2_powerpc,
41 .stage2_riscv64,
42 .stage2_spirv,
43 => false,
44 else => true,
45};
46
47fn print(comptime fmt: []const u8, args: anytype) void {
48 if (@inComptime()) {
49 @compileError(std.fmt.comptimePrint(fmt, args));
50 } else if (backend_can_print) {
51 std.debug.print(fmt, args);
52 }
53}
54
55/// This function is intended to be used only in tests. It prints diagnostics to stderr
56/// and then returns a test failure error when actual_error_union is not expected_error.
57pub fn expectError(expected_error: anyerror, actual_error_union: anytype) !void {
58 if (actual_error_union) |actual_payload| {
59 print("expected error.{s}, found {any}\n", .{ @errorName(expected_error), actual_payload });
60 return error.TestExpectedError;
61 } else |actual_error| {
62 if (expected_error != actual_error) {
63 print("expected error.{s}, found error.{s}\n", .{
64 @errorName(expected_error),
65 @errorName(actual_error),
66 });
67 return error.TestUnexpectedError;
68 }
69 }
70}
71
72/// This function is intended to be used only in tests. When the two values are not
73/// equal, prints diagnostics to stderr to show exactly how they are not equal,
74/// then returns a test failure error.
75/// `actual` and `expected` are coerced to a common type using peer type resolution.
76pub inline fn expectEqual(expected: anytype, actual: anytype) !void {
77 const T = @TypeOf(expected, actual);
78 return expectEqualInner(T, expected, actual);
79}
80
81fn expectEqualInner(comptime T: type, expected: T, actual: T) !void {
82 switch (@typeInfo(@TypeOf(actual))) {
83 .noreturn,
84 .@"opaque",
85 .frame,
86 .@"anyframe",
87 => @compileError("value of type " ++ @typeName(@TypeOf(actual)) ++ " encountered"),
88
89 .undefined,
90 .null,
91 .void,
92 => return,
93
94 .type => {
95 if (actual != expected) {
96 print("expected type {s}, found type {s}\n", .{ @typeName(expected), @typeName(actual) });
97 return error.TestExpectedEqual;
98 }
99 },
100
101 .bool,
102 .int,
103 .float,
104 .comptime_float,
105 .comptime_int,
106 .enum_literal,
107 .@"enum",
108 .@"fn",
109 .error_set,
110 => {
111 if (actual != expected) {
112 print("expected {any}, found {any}\n", .{ expected, actual });
113 return error.TestExpectedEqual;
114 }
115 },
116
117 .pointer => |pointer| {
118 switch (pointer.size) {
119 .one, .many, .c => {
120 if (actual != expected) {
121 print("expected {*}, found {*}\n", .{ expected, actual });
122 return error.TestExpectedEqual;
123 }
124 },
125 .slice => {
126 if (actual.ptr != expected.ptr) {
127 print("expected slice ptr {*}, found {*}\n", .{ expected.ptr, actual.ptr });
128 return error.TestExpectedEqual;
129 }
130 if (actual.len != expected.len) {
131 print("expected slice len {}, found {}\n", .{ expected.len, actual.len });
132 return error.TestExpectedEqual;
133 }
134 },
135 }
136 },
137
138 .array => |array| try expectEqualSlices(array.child, &expected, &actual),
139
140 .vector => |info| {
141 const expect_array: [info.len]info.child = expected;
142 const actual_array: [info.len]info.child = actual;
143 try expectEqualSlices(info.child, &expect_array, &actual_array);
144 },
145
146 .@"struct" => |structType| {
147 inline for (structType.fields) |field| {
148 try expectEqual(@field(expected, field.name), @field(actual, field.name));
149 }
150 },
151
152 .@"union" => |union_info| {
153 if (union_info.tag_type == null) {
154 const first_size = @bitSizeOf(union_info.fields[0].type);
155 inline for (union_info.fields) |field| {
156 if (@bitSizeOf(field.type) != first_size) {
157 @compileError("Unable to compare untagged unions with varying field sizes for type " ++ @typeName(@TypeOf(actual)));
158 }
159 }
160
161 const BackingInt = std.meta.Int(.unsigned, @bitSizeOf(T));
162 return expectEqual(
163 @as(BackingInt, @bitCast(expected)),
164 @as(BackingInt, @bitCast(actual)),
165 );
166 }
167
168 const Tag = std.meta.Tag(@TypeOf(expected));
169
170 const expectedTag = @as(Tag, expected);
171 const actualTag = @as(Tag, actual);
172
173 try expectEqual(expectedTag, actualTag);
174
175 // we only reach this switch if the tags are equal
176 switch (expected) {
177 inline else => |val, tag| try expectEqual(val, @field(actual, @tagName(tag))),
178 }
179 },
180
181 .optional => {
182 if (expected) |expected_payload| {
183 if (actual) |actual_payload| {
184 try expectEqual(expected_payload, actual_payload);
185 } else {
186 print("expected {any}, found null\n", .{expected_payload});
187 return error.TestExpectedEqual;
188 }
189 } else {
190 if (actual) |actual_payload| {
191 print("expected null, found {any}\n", .{actual_payload});
192 return error.TestExpectedEqual;
193 }
194 }
195 },
196
197 .error_union => {
198 if (expected) |expected_payload| {
199 if (actual) |actual_payload| {
200 try expectEqual(expected_payload, actual_payload);
201 } else |actual_err| {
202 print("expected {any}, found {}\n", .{ expected_payload, actual_err });
203 return error.TestExpectedEqual;
204 }
205 } else |expected_err| {
206 if (actual) |actual_payload| {
207 print("expected {}, found {any}\n", .{ expected_err, actual_payload });
208 return error.TestExpectedEqual;
209 } else |actual_err| {
210 try expectEqual(expected_err, actual_err);
211 }
212 }
213 },
214 }
215}
216
217test "expectEqual.union(enum)" {
218 const T = union(enum) {
219 a: i32,
220 b: f32,
221 };
222
223 const a10 = T{ .a = 10 };
224
225 try expectEqual(a10, a10);
226}
227
228test "expectEqual union with comptime-only field" {
229 const U = union(enum) {
230 a: void,
231 b: void,
232 c: comptime_int,
233 };
234
235 try expectEqual(U{ .a = {} }, .a);
236}
237
238test "expectEqual nested array" {
239 const a = [2][2]f32{
240 [_]f32{ 1.0, 0.0 },
241 [_]f32{ 0.0, 1.0 },
242 };
243
244 const b = [2][2]f32{
245 [_]f32{ 1.0, 0.0 },
246 [_]f32{ 0.0, 1.0 },
247 };
248
249 try expectEqual(a, b);
250}
251
252test "expectEqual vector" {
253 const a: @Vector(4, u32) = @splat(4);
254 const b: @Vector(4, u32) = @splat(4);
255
256 try expectEqual(a, b);
257}
258
259test "expectEqual null" {
260 const a = .{null};
261 const b = @Vector(1, ?*u8){null};
262
263 try expectEqual(a, b);
264}
265
266/// This function is intended to be used only in tests. When the formatted result of the template
267/// and its arguments does not equal the expected text, it prints diagnostics to stderr to show how
268/// they are not equal, then returns an error. It depends on `expectEqualStrings` for printing
269/// diagnostics.
270pub fn expectFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void {
271 if (@inComptime()) {
272 var buffer: [std.fmt.count(template, args)]u8 = undefined;
273 return expectEqualStrings(expected, try std.fmt.bufPrint(&buffer, template, args));
274 }
275 const actual = try std.fmt.allocPrint(allocator, template, args);
276 defer allocator.free(actual);
277 return expectEqualStrings(expected, actual);
278}
279
280/// This function is intended to be used only in tests. When the actual value is
281/// not approximately equal to the expected value, prints diagnostics to stderr
282/// to show exactly how they are not equal, then returns a test failure error.
283/// See `math.approxEqAbs` for more information on the tolerance parameter.
284/// The types must be floating-point.
285/// `actual` and `expected` are coerced to a common type using peer type resolution.
286pub inline fn expectApproxEqAbs(expected: anytype, actual: anytype, tolerance: anytype) !void {
287 const T = @TypeOf(expected, actual, tolerance);
288 return expectApproxEqAbsInner(T, expected, actual, tolerance);
289}
290
291fn expectApproxEqAbsInner(comptime T: type, expected: T, actual: T, tolerance: T) !void {
292 switch (@typeInfo(T)) {
293 .float => if (!math.approxEqAbs(T, expected, actual, tolerance)) {
294 print("actual {}, not within absolute tolerance {} of expected {}\n", .{ actual, tolerance, expected });
295 return error.TestExpectedApproxEqAbs;
296 },
297
298 .comptime_float => @compileError("Cannot approximately compare two comptime_float values"),
299
300 else => @compileError("Unable to compare non floating point values"),
301 }
302}
303
304test expectApproxEqAbs {
305 inline for ([_]type{ f16, f32, f64, f128 }) |T| {
306 const pos_x: T = 12.0;
307 const pos_y: T = 12.06;
308 const neg_x: T = -12.0;
309 const neg_y: T = -12.06;
310
311 try expectApproxEqAbs(pos_x, pos_y, 0.1);
312 try expectApproxEqAbs(neg_x, neg_y, 0.1);
313 }
314}
315
316/// This function is intended to be used only in tests. When the actual value is
317/// not approximately equal to the expected value, prints diagnostics to stderr
318/// to show exactly how they are not equal, then returns a test failure error.
319/// See `math.approxEqRel` for more information on the tolerance parameter.
320/// The types must be floating-point.
321/// `actual` and `expected` are coerced to a common type using peer type resolution.
322pub inline fn expectApproxEqRel(expected: anytype, actual: anytype, tolerance: anytype) !void {
323 const T = @TypeOf(expected, actual, tolerance);
324 return expectApproxEqRelInner(T, expected, actual, tolerance);
325}
326
327fn expectApproxEqRelInner(comptime T: type, expected: T, actual: T, tolerance: T) !void {
328 switch (@typeInfo(T)) {
329 .float => if (!math.approxEqRel(T, expected, actual, tolerance)) {
330 print("actual {}, not within relative tolerance {} of expected {}\n", .{ actual, tolerance, expected });
331 return error.TestExpectedApproxEqRel;
332 },
333
334 .comptime_float => @compileError("Cannot approximately compare two comptime_float values"),
335
336 else => @compileError("Unable to compare non floating point values"),
337 }
338}
339
340test expectApproxEqRel {
341 inline for ([_]type{ f16, f32, f64, f128 }) |T| {
342 const eps_value = comptime math.floatEps(T);
343 const sqrt_eps_value = comptime @sqrt(eps_value);
344
345 const pos_x: T = 12.0;
346 const pos_y: T = pos_x + 2 * eps_value;
347 const neg_x: T = -12.0;
348 const neg_y: T = neg_x - 2 * eps_value;
349
350 try expectApproxEqRel(pos_x, pos_y, sqrt_eps_value);
351 try expectApproxEqRel(neg_x, neg_y, sqrt_eps_value);
352 }
353}
354
355/// This function is intended to be used only in tests. When the two slices are not
356/// equal, prints diagnostics to stderr to show exactly how they are not equal (with
357/// the differences highlighted in red), then returns a test failure error.
358/// The colorized output is optional and controlled by the return of `std.Io.tty.Config.detect`.
359/// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead.
360pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void {
361 const diff_index: usize = diff_index: {
362 const shortest = @min(expected.len, actual.len);
363 var index: usize = 0;
364 while (index < shortest) : (index += 1) {
365 if (!std.meta.eql(actual[index], expected[index])) break :diff_index index;
366 }
367 break :diff_index if (expected.len == actual.len) return else shortest;
368 };
369 if (!backend_can_print) return error.TestExpectedEqual;
370 const stderr_w, const ttyconf = std.debug.lockStderrWriter(&.{});
371 defer std.debug.unlockStderrWriter();
372 failEqualSlices(T, expected, actual, diff_index, stderr_w, ttyconf) catch {};
373 return error.TestExpectedEqual;
374}
375
376fn failEqualSlices(
377 comptime T: type,
378 expected: []const T,
379 actual: []const T,
380 diff_index: usize,
381 w: *std.Io.Writer,
382 ttyconf: std.Io.tty.Config,
383) !void {
384 try w.print("slices differ. first difference occurs at index {d} (0x{X})\n", .{ diff_index, diff_index });
385
386 // TODO: Should this be configurable by the caller?
387 const max_lines: usize = 16;
388 const max_window_size: usize = if (T == u8) max_lines * 16 else max_lines;
389
390 // Print a maximum of max_window_size items of each input, starting just before the
391 // first difference to give a bit of context.
392 var window_start: usize = 0;
393 if (@max(actual.len, expected.len) > max_window_size) {
394 const alignment = if (T == u8) 16 else 2;
395 window_start = std.mem.alignBackward(usize, diff_index - @min(diff_index, alignment), alignment);
396 }
397 const expected_window = expected[window_start..@min(expected.len, window_start + max_window_size)];
398 const expected_truncated = window_start + expected_window.len < expected.len;
399 const actual_window = actual[window_start..@min(actual.len, window_start + max_window_size)];
400 const actual_truncated = window_start + actual_window.len < actual.len;
401
402 var differ = if (T == u8) BytesDiffer{
403 .expected = expected_window,
404 .actual = actual_window,
405 .ttyconf = ttyconf,
406 } else SliceDiffer(T){
407 .start_index = window_start,
408 .expected = expected_window,
409 .actual = actual_window,
410 .ttyconf = ttyconf,
411 };
412
413 // Print indexes as hex for slices of u8 since it's more likely to be binary data where
414 // that is usually useful.
415 const index_fmt = if (T == u8) "0x{X}" else "{}";
416
417 try w.print("\n============ expected this output: ============= len: {} (0x{X})\n\n", .{ expected.len, expected.len });
418 if (window_start > 0) {
419 if (T == u8) {
420 try w.print("... truncated, start index: " ++ index_fmt ++ " ...\n", .{window_start});
421 } else {
422 try w.print("... truncated ...\n", .{});
423 }
424 }
425 differ.write(w) catch {};
426 if (expected_truncated) {
427 const end_offset = window_start + expected_window.len;
428 const num_missing_items = expected.len - (window_start + expected_window.len);
429 if (T == u8) {
430 try w.print("... truncated, indexes [" ++ index_fmt ++ "..] not shown, remaining bytes: " ++ index_fmt ++ " ...\n", .{ end_offset, num_missing_items });
431 } else {
432 try w.print("... truncated, remaining items: " ++ index_fmt ++ " ...\n", .{num_missing_items});
433 }
434 }
435
436 // now reverse expected/actual and print again
437 differ.expected = actual_window;
438 differ.actual = expected_window;
439 try w.print("\n============= instead found this: ============== len: {} (0x{X})\n\n", .{ actual.len, actual.len });
440 if (window_start > 0) {
441 if (T == u8) {
442 try w.print("... truncated, start index: " ++ index_fmt ++ " ...\n", .{window_start});
443 } else {
444 try w.print("... truncated ...\n", .{});
445 }
446 }
447 differ.write(w) catch {};
448 if (actual_truncated) {
449 const end_offset = window_start + actual_window.len;
450 const num_missing_items = actual.len - (window_start + actual_window.len);
451 if (T == u8) {
452 try w.print("... truncated, indexes [" ++ index_fmt ++ "..] not shown, remaining bytes: " ++ index_fmt ++ " ...\n", .{ end_offset, num_missing_items });
453 } else {
454 try w.print("... truncated, remaining items: " ++ index_fmt ++ " ...\n", .{num_missing_items});
455 }
456 }
457 try w.print("\n================================================\n\n", .{});
458
459 return error.TestExpectedEqual;
460}
461
462fn SliceDiffer(comptime T: type) type {
463 return struct {
464 start_index: usize,
465 expected: []const T,
466 actual: []const T,
467 ttyconf: std.Io.tty.Config,
468
469 const Self = @This();
470
471 pub fn write(self: Self, writer: *std.Io.Writer) !void {
472 for (self.expected, 0..) |value, i| {
473 const full_index = self.start_index + i;
474 const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true;
475 if (diff) try self.ttyconf.setColor(writer, .red);
476 if (@typeInfo(T) == .pointer) {
477 try writer.print("[{}]{*}: {any}\n", .{ full_index, value, value });
478 } else {
479 try writer.print("[{}]: {any}\n", .{ full_index, value });
480 }
481 if (diff) try self.ttyconf.setColor(writer, .reset);
482 }
483 }
484 };
485}
486
487const BytesDiffer = struct {
488 expected: []const u8,
489 actual: []const u8,
490 ttyconf: std.Io.tty.Config,
491
492 pub fn write(self: BytesDiffer, writer: *std.Io.Writer) !void {
493 var expected_iterator = std.mem.window(u8, self.expected, 16, 16);
494 var row: usize = 0;
495 while (expected_iterator.next()) |chunk| {
496 // to avoid having to calculate diffs twice per chunk
497 var diffs: std.bit_set.IntegerBitSet(16) = .{ .mask = 0 };
498 for (chunk, 0..) |byte, col| {
499 const absolute_byte_index = col + row * 16;
500 const diff = if (absolute_byte_index < self.actual.len) self.actual[absolute_byte_index] != byte else true;
501 if (diff) diffs.set(col);
502 try self.writeDiff(writer, "{X:0>2} ", .{byte}, diff);
503 if (col == 7) try writer.writeByte(' ');
504 }
505 try writer.writeByte(' ');
506 if (chunk.len < 16) {
507 var missing_columns = (16 - chunk.len) * 3;
508 if (chunk.len < 8) missing_columns += 1;
509 try writer.splatByteAll(' ', missing_columns);
510 }
511 for (chunk, 0..) |byte, col| {
512 const diff = diffs.isSet(col);
513 if (std.ascii.isPrint(byte)) {
514 try self.writeDiff(writer, "{c}", .{byte}, diff);
515 } else {
516 // TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed
517 if (self.ttyconf == .windows_api) {
518 try self.writeDiff(writer, ".", .{}, diff);
519 continue;
520 }
521
522 // Let's print some common control codes as graphical Unicode symbols.
523 // We don't want to do this for all control codes because most control codes apart from
524 // the ones that Zig has escape sequences for are likely not very useful to print as symbols.
525 switch (byte) {
526 '\n' => try self.writeDiff(writer, "␊", .{}, diff),
527 '\r' => try self.writeDiff(writer, "␍", .{}, diff),
528 '\t' => try self.writeDiff(writer, "␉", .{}, diff),
529 else => try self.writeDiff(writer, ".", .{}, diff),
530 }
531 }
532 }
533 try writer.writeByte('\n');
534 row += 1;
535 }
536 }
537
538 fn writeDiff(self: BytesDiffer, writer: *std.Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void {
539 if (diff) try self.ttyconf.setColor(writer, .red);
540 try writer.print(fmt, args);
541 if (diff) try self.ttyconf.setColor(writer, .reset);
542 }
543};
544
545test {
546 try expectEqualSlices(u8, "foo\x00", "foo\x00");
547 try expectEqualSlices(u16, &[_]u16{ 100, 200, 300, 400 }, &[_]u16{ 100, 200, 300, 400 });
548 const E = enum { foo, bar };
549 const S = struct {
550 v: E,
551 };
552 try expectEqualSlices(
553 S,
554 &[_]S{ .{ .v = .foo }, .{ .v = .bar }, .{ .v = .foo }, .{ .v = .bar } },
555 &[_]S{ .{ .v = .foo }, .{ .v = .bar }, .{ .v = .foo }, .{ .v = .bar } },
556 );
557}
558
559/// This function is intended to be used only in tests. Checks that two slices or two arrays are equal,
560/// including that their sentinel (if any) are the same. Will error if given another type.
561pub fn expectEqualSentinel(comptime T: type, comptime sentinel: T, expected: [:sentinel]const T, actual: [:sentinel]const T) !void {
562 try expectEqualSlices(T, expected, actual);
563
564 const expected_value_sentinel = blk: {
565 switch (@typeInfo(@TypeOf(expected))) {
566 .pointer => {
567 break :blk expected[expected.len];
568 },
569 .array => |array_info| {
570 const indexable_outside_of_bounds = @as([]const array_info.child, &expected);
571 break :blk indexable_outside_of_bounds[indexable_outside_of_bounds.len];
572 },
573 else => {},
574 }
575 };
576
577 const actual_value_sentinel = blk: {
578 switch (@typeInfo(@TypeOf(actual))) {
579 .pointer => {
580 break :blk actual[actual.len];
581 },
582 .array => |array_info| {
583 const indexable_outside_of_bounds = @as([]const array_info.child, &actual);
584 break :blk indexable_outside_of_bounds[indexable_outside_of_bounds.len];
585 },
586 else => {},
587 }
588 };
589
590 if (!std.meta.eql(sentinel, expected_value_sentinel)) {
591 print("expectEqualSentinel: 'expected' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {}\n", .{ sentinel, expected_value_sentinel });
592 return error.TestExpectedEqual;
593 }
594
595 if (!std.meta.eql(sentinel, actual_value_sentinel)) {
596 print("expectEqualSentinel: 'actual' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {}\n", .{ sentinel, actual_value_sentinel });
597 return error.TestExpectedEqual;
598 }
599}
600
601/// This function is intended to be used only in tests.
602/// When `ok` is false, returns a test failure error.
603pub fn expect(ok: bool) !void {
604 if (!ok) return error.TestUnexpectedResult;
605}
606
607pub const TmpDir = struct {
608 dir: std.fs.Dir,
609 parent_dir: std.fs.Dir,
610 sub_path: [sub_path_len]u8,
611
612 const random_bytes_count = 12;
613 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
614
615 pub fn cleanup(self: *TmpDir) void {
616 self.dir.close();
617 self.parent_dir.deleteTree(&self.sub_path) catch {};
618 self.parent_dir.close();
619 self.* = undefined;
620 }
621};
622
623pub fn tmpDir(opts: std.fs.Dir.OpenOptions) TmpDir {
624 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
625 std.crypto.random.bytes(&random_bytes);
626 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
627 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
628
629 const cwd = std.fs.cwd();
630 var cache_dir = cwd.makeOpenPath(".zig-cache", .{}) catch
631 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");
632 defer cache_dir.close();
633 const parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch
634 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir");
635 const dir = parent_dir.makeOpenPath(&sub_path, opts) catch
636 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
637
638 return .{
639 .dir = dir,
640 .parent_dir = parent_dir,
641 .sub_path = sub_path,
642 };
643}
644
645pub fn expectEqualStrings(expected: []const u8, actual: []const u8) !void {
646 if (std.mem.indexOfDiff(u8, actual, expected)) |diff_index| {
647 if (@inComptime()) {
648 @compileError(std.fmt.comptimePrint("\nexpected:\n{s}\nfound:\n{s}\ndifference starts at index {d}", .{
649 expected, actual, diff_index,
650 }));
651 }
652 print("\n====== expected this output: =========\n", .{});
653 printWithVisibleNewlines(expected);
654 print("\n======== instead found this: =========\n", .{});
655 printWithVisibleNewlines(actual);
656 print("\n======================================\n", .{});
657
658 var diff_line_number: usize = 1;
659 for (expected[0..diff_index]) |value| {
660 if (value == '\n') diff_line_number += 1;
661 }
662 print("First difference occurs on line {d}:\n", .{diff_line_number});
663
664 print("expected:\n", .{});
665 printIndicatorLine(expected, diff_index);
666
667 print("found:\n", .{});
668 printIndicatorLine(actual, diff_index);
669
670 return error.TestExpectedEqual;
671 }
672}
673
674pub fn expectStringStartsWith(actual: []const u8, expected_starts_with: []const u8) !void {
675 if (std.mem.startsWith(u8, actual, expected_starts_with))
676 return;
677
678 const shortened_actual = if (actual.len >= expected_starts_with.len)
679 actual[0..expected_starts_with.len]
680 else
681 actual;
682
683 print("\n====== expected to start with: =========\n", .{});
684 printWithVisibleNewlines(expected_starts_with);
685 print("\n====== instead started with: ===========\n", .{});
686 printWithVisibleNewlines(shortened_actual);
687 print("\n========= full output: ==============\n", .{});
688 printWithVisibleNewlines(actual);
689 print("\n======================================\n", .{});
690
691 return error.TestExpectedStartsWith;
692}
693
694pub fn expectStringEndsWith(actual: []const u8, expected_ends_with: []const u8) !void {
695 if (std.mem.endsWith(u8, actual, expected_ends_with))
696 return;
697
698 const shortened_actual = if (actual.len >= expected_ends_with.len)
699 actual[(actual.len - expected_ends_with.len)..]
700 else
701 actual;
702
703 print("\n====== expected to end with: =========\n", .{});
704 printWithVisibleNewlines(expected_ends_with);
705 print("\n====== instead ended with: ===========\n", .{});
706 printWithVisibleNewlines(shortened_actual);
707 print("\n========= full output: ==============\n", .{});
708 printWithVisibleNewlines(actual);
709 print("\n======================================\n", .{});
710
711 return error.TestExpectedEndsWith;
712}
713
714/// This function is intended to be used only in tests. When the two values are not
715/// deeply equal, prints diagnostics to stderr to show exactly how they are not equal,
716/// then returns a test failure error.
717/// `actual` and `expected` are coerced to a common type using peer type resolution.
718///
719/// Deeply equal is defined as follows:
720/// Primitive types are deeply equal if they are equal using `==` operator.
721/// Struct values are deeply equal if their corresponding fields are deeply equal.
722/// Container types(like Array/Slice/Vector) deeply equal when their corresponding elements are deeply equal.
723/// Pointer values are deeply equal if values they point to are deeply equal.
724///
725/// Note: Self-referential structs are supported (e.g. things like std.SinglyLinkedList)
726/// but may cause infinite recursion or stack overflow when a container has a pointer to itself.
727pub inline fn expectEqualDeep(expected: anytype, actual: anytype) error{TestExpectedEqual}!void {
728 const T = @TypeOf(expected, actual);
729 return expectEqualDeepInner(T, expected, actual);
730}
731
732fn expectEqualDeepInner(comptime T: type, expected: T, actual: T) error{TestExpectedEqual}!void {
733 switch (@typeInfo(@TypeOf(actual))) {
734 .noreturn,
735 .@"opaque",
736 .frame,
737 .@"anyframe",
738 => @compileError("value of type " ++ @typeName(@TypeOf(actual)) ++ " encountered"),
739
740 .undefined,
741 .null,
742 .void,
743 => return,
744
745 .type => {
746 if (actual != expected) {
747 print("expected type {s}, found type {s}\n", .{ @typeName(expected), @typeName(actual) });
748 return error.TestExpectedEqual;
749 }
750 },
751
752 .bool,
753 .int,
754 .float,
755 .comptime_float,
756 .comptime_int,
757 .enum_literal,
758 .@"enum",
759 .@"fn",
760 .error_set,
761 => {
762 if (actual != expected) {
763 print("expected {any}, found {any}\n", .{ expected, actual });
764 return error.TestExpectedEqual;
765 }
766 },
767
768 .pointer => |pointer| {
769 switch (pointer.size) {
770 // We have no idea what is behind those pointers, so the best we can do is `==` check.
771 .c, .many => {
772 if (actual != expected) {
773 print("expected {*}, found {*}\n", .{ expected, actual });
774 return error.TestExpectedEqual;
775 }
776 },
777 .one => {
778 // Length of those pointers are runtime value, so the best we can do is `==` check.
779 switch (@typeInfo(pointer.child)) {
780 .@"fn", .@"opaque" => {
781 if (actual != expected) {
782 print("expected {*}, found {*}\n", .{ expected, actual });
783 return error.TestExpectedEqual;
784 }
785 },
786 else => try expectEqualDeep(expected.*, actual.*),
787 }
788 },
789 .slice => {
790 if (expected.len != actual.len) {
791 print("Slice len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len });
792 return error.TestExpectedEqual;
793 }
794 var i: usize = 0;
795 while (i < expected.len) : (i += 1) {
796 expectEqualDeep(expected[i], actual[i]) catch |e| {
797 print("index {d} incorrect. expected {any}, found {any}\n", .{
798 i, expected[i], actual[i],
799 });
800 return e;
801 };
802 }
803 },
804 }
805 },
806
807 .array => |_| {
808 if (expected.len != actual.len) {
809 print("Array len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len });
810 return error.TestExpectedEqual;
811 }
812 var i: usize = 0;
813 while (i < expected.len) : (i += 1) {
814 expectEqualDeep(expected[i], actual[i]) catch |e| {
815 print("index {d} incorrect. expected {any}, found {any}\n", .{
816 i, expected[i], actual[i],
817 });
818 return e;
819 };
820 }
821 },
822
823 .vector => |info| {
824 if (info.len != @typeInfo(@TypeOf(actual)).vector.len) {
825 print("Vector len not the same, expected {d}, found {d}\n", .{ info.len, @typeInfo(@TypeOf(actual)).vector.len });
826 return error.TestExpectedEqual;
827 }
828 inline for (0..info.len) |i| {
829 expectEqualDeep(expected[i], actual[i]) catch |e| {
830 print("index {d} incorrect. expected {any}, found {any}\n", .{
831 i, expected[i], actual[i],
832 });
833 return e;
834 };
835 }
836 },
837
838 .@"struct" => |structType| {
839 inline for (structType.fields) |field| {
840 expectEqualDeep(@field(expected, field.name), @field(actual, field.name)) catch |e| {
841 print("Field {s} incorrect. expected {any}, found {any}\n", .{ field.name, @field(expected, field.name), @field(actual, field.name) });
842 return e;
843 };
844 }
845 },
846
847 .@"union" => |union_info| {
848 if (union_info.tag_type == null) {
849 @compileError("Unable to compare untagged union values for type " ++ @typeName(@TypeOf(actual)));
850 }
851
852 const Tag = std.meta.Tag(@TypeOf(expected));
853
854 const expectedTag = @as(Tag, expected);
855 const actualTag = @as(Tag, actual);
856
857 try expectEqual(expectedTag, actualTag);
858
859 // we only reach this switch if the tags are equal
860 switch (expected) {
861 inline else => |val, tag| {
862 try expectEqualDeep(val, @field(actual, @tagName(tag)));
863 },
864 }
865 },
866
867 .optional => {
868 if (expected) |expected_payload| {
869 if (actual) |actual_payload| {
870 try expectEqualDeep(expected_payload, actual_payload);
871 } else {
872 print("expected {any}, found null\n", .{expected_payload});
873 return error.TestExpectedEqual;
874 }
875 } else {
876 if (actual) |actual_payload| {
877 print("expected null, found {any}\n", .{actual_payload});
878 return error.TestExpectedEqual;
879 }
880 }
881 },
882
883 .error_union => {
884 if (expected) |expected_payload| {
885 if (actual) |actual_payload| {
886 try expectEqualDeep(expected_payload, actual_payload);
887 } else |actual_err| {
888 print("expected {any}, found {any}\n", .{ expected_payload, actual_err });
889 return error.TestExpectedEqual;
890 }
891 } else |expected_err| {
892 if (actual) |actual_payload| {
893 print("expected {any}, found {any}\n", .{ expected_err, actual_payload });
894 return error.TestExpectedEqual;
895 } else |actual_err| {
896 try expectEqualDeep(expected_err, actual_err);
897 }
898 }
899 },
900 }
901}
902
903test "expectEqualDeep primitive type" {
904 try expectEqualDeep(1, 1);
905 try expectEqualDeep(true, true);
906 try expectEqualDeep(1.5, 1.5);
907 try expectEqualDeep(u8, u8);
908 try expectEqualDeep(error.Bad, error.Bad);
909
910 // optional
911 {
912 const foo: ?u32 = 1;
913 const bar: ?u32 = 1;
914 try expectEqualDeep(foo, bar);
915 try expectEqualDeep(?u32, ?u32);
916 }
917 // function type
918 {
919 const fnType = struct {
920 fn foo() void {
921 unreachable;
922 }
923 }.foo;
924 try expectEqualDeep(fnType, fnType);
925 }
926 // enum with formatter
927 {
928 const TestEnum = enum {
929 a,
930 b,
931
932 pub fn format(self: @This(), writer: *std.Io.Writer) !void {
933 try writer.writeAll(@tagName(self));
934 }
935 };
936 try expectEqualDeep(TestEnum.b, TestEnum.b);
937 }
938}
939
940test "expectEqualDeep pointer" {
941 const a = 1;
942 const b = 1;
943 try expectEqualDeep(&a, &b);
944}
945
946test "expectEqualDeep composite type" {
947 try expectEqualDeep("abc", "abc");
948 const s1: []const u8 = "abc";
949 const s2 = "abcd";
950 const s3: []const u8 = s2[0..3];
951 try expectEqualDeep(s1, s3);
952
953 const TestStruct = struct { s: []const u8 };
954 try expectEqualDeep(TestStruct{ .s = "abc" }, TestStruct{ .s = "abc" });
955 try expectEqualDeep([_][]const u8{ "a", "b", "c" }, [_][]const u8{ "a", "b", "c" });
956
957 // vector
958 try expectEqualDeep(@as(@Vector(4, u32), @splat(4)), @as(@Vector(4, u32), @splat(4)));
959
960 // nested array
961 {
962 const a = [2][2]f32{
963 [_]f32{ 1.0, 0.0 },
964 [_]f32{ 0.0, 1.0 },
965 };
966
967 const b = [2][2]f32{
968 [_]f32{ 1.0, 0.0 },
969 [_]f32{ 0.0, 1.0 },
970 };
971
972 try expectEqualDeep(a, b);
973 try expectEqualDeep(&a, &b);
974 }
975
976 // inferred union
977 const TestStruct2 = struct {
978 const A = union(enum) { b: B, c: C };
979 const B = struct {};
980 const C = struct { a: *const A };
981 };
982
983 const union1 = TestStruct2.A{ .b = .{} };
984 try expectEqualDeep(
985 TestStruct2.A{ .c = .{ .a = &union1 } },
986 TestStruct2.A{ .c = .{ .a = &union1 } },
987 );
988}
989
990fn printIndicatorLine(source: []const u8, indicator_index: usize) void {
991 const line_begin_index = if (std.mem.lastIndexOfScalar(u8, source[0..indicator_index], '\n')) |line_begin|
992 line_begin + 1
993 else
994 0;
995 const line_end_index = if (std.mem.indexOfScalar(u8, source[indicator_index..], '\n')) |line_end|
996 (indicator_index + line_end)
997 else
998 source.len;
999
1000 printLine(source[line_begin_index..line_end_index]);
1001 for (line_begin_index..indicator_index) |_|
1002 print(" ", .{});
1003 if (indicator_index >= source.len)
1004 print("^ (end of string)\n", .{})
1005 else
1006 print("^ ('\\x{x:0>2}')\n", .{source[indicator_index]});
1007}
1008
1009fn printWithVisibleNewlines(source: []const u8) void {
1010 var i: usize = 0;
1011 while (std.mem.indexOfScalar(u8, source[i..], '\n')) |nl| : (i += nl + 1) {
1012 printLine(source[i..][0..nl]);
1013 }
1014 print("{s}␃\n", .{source[i..]}); // End of Text symbol (ETX)
1015}
1016
1017fn printLine(line: []const u8) void {
1018 if (line.len != 0) switch (line[line.len - 1]) {
1019 ' ', '\t' => return print("{s}⏎\n", .{line}), // Return symbol
1020 else => {},
1021 };
1022 print("{s}\n", .{line});
1023}
1024
1025test {
1026 try expectEqualStrings("foo", "foo");
1027}
1028
1029/// Exhaustively check that allocation failures within `test_fn` are handled without
1030/// introducing memory leaks. If used with the `testing.allocator` as the `backing_allocator`,
1031/// it will also be able to detect double frees, etc (when runtime safety is enabled).
1032///
1033/// The provided `test_fn` must have a `std.mem.Allocator` as its first argument,
1034/// and must have a return type of `!void`. Any extra arguments of `test_fn` can
1035/// be provided via the `extra_args` tuple.
1036///
1037/// Any relevant state shared between runs of `test_fn` *must* be reset within `test_fn`.
1038///
1039/// The strategy employed is to:
1040/// - Run the test function once to get the total number of allocations.
1041/// - Then, iterate and run the function X more times, incrementing
1042/// the failing index each iteration (where X is the total number of
1043/// allocations determined previously)
1044///
1045/// Expects that `test_fn` has a deterministic number of memory allocations:
1046/// - If an allocation was made to fail during a run of `test_fn`, but `test_fn`
1047/// didn't return `error.OutOfMemory`, then `error.SwallowedOutOfMemoryError`
1048/// is returned from `checkAllAllocationFailures`. You may want to ignore this
1049/// depending on whether or not the code you're testing includes some strategies
1050/// for recovering from `error.OutOfMemory`.
1051/// - If a run of `test_fn` with an expected allocation failure executes without
1052/// an allocation failure being induced, then `error.NondeterministicMemoryUsage`
1053/// is returned. This error means that there are allocation points that won't be
1054/// tested by the strategy this function employs (that is, there are sometimes more
1055/// points of allocation than the initial run of `test_fn` detects).
1056///
1057/// ---
1058///
1059/// Here's an example using a simple test case that will cause a leak when the
1060/// allocation of `bar` fails (but will pass normally):
1061///
1062/// ```zig
1063/// test {
1064/// const length: usize = 10;
1065/// const allocator = std.testing.allocator;
1066/// var foo = try allocator.alloc(u8, length);
1067/// var bar = try allocator.alloc(u8, length);
1068///
1069/// allocator.free(foo);
1070/// allocator.free(bar);
1071/// }
1072/// ```
1073///
1074/// The test case can be converted to something that this function can use by
1075/// doing:
1076///
1077/// ```zig
1078/// fn testImpl(allocator: std.mem.Allocator, length: usize) !void {
1079/// var foo = try allocator.alloc(u8, length);
1080/// var bar = try allocator.alloc(u8, length);
1081///
1082/// allocator.free(foo);
1083/// allocator.free(bar);
1084/// }
1085///
1086/// test {
1087/// const length: usize = 10;
1088/// const allocator = std.testing.allocator;
1089/// try std.testing.checkAllAllocationFailures(allocator, testImpl, .{length});
1090/// }
1091/// ```
1092///
1093/// Running this test will show that `foo` is leaked when the allocation of
1094/// `bar` fails. The simplest fix, in this case, would be to use defer like so:
1095///
1096/// ```zig
1097/// fn testImpl(allocator: std.mem.Allocator, length: usize) !void {
1098/// var foo = try allocator.alloc(u8, length);
1099/// defer allocator.free(foo);
1100/// var bar = try allocator.alloc(u8, length);
1101/// defer allocator.free(bar);
1102/// }
1103/// ```
1104pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime test_fn: anytype, extra_args: anytype) !void {
1105 switch (@typeInfo(@typeInfo(@TypeOf(test_fn)).@"fn".return_type.?)) {
1106 .error_union => |info| {
1107 if (info.payload != void) {
1108 @compileError("Return type must be !void");
1109 }
1110 },
1111 else => @compileError("Return type must be !void"),
1112 }
1113 if (@typeInfo(@TypeOf(extra_args)) != .@"struct") {
1114 @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(extra_args)));
1115 }
1116
1117 const ArgsTuple = std.meta.ArgsTuple(@TypeOf(test_fn));
1118 const fn_args_fields = @typeInfo(ArgsTuple).@"struct".fields;
1119 if (fn_args_fields.len == 0 or fn_args_fields[0].type != std.mem.Allocator) {
1120 @compileError("The provided function must have an " ++ @typeName(std.mem.Allocator) ++ " as its first argument");
1121 }
1122 const expected_args_tuple_len = fn_args_fields.len - 1;
1123 if (extra_args.len != expected_args_tuple_len) {
1124 @compileError("The provided function expects " ++ std.fmt.comptimePrint("{d}", .{expected_args_tuple_len}) ++ " extra arguments, but the provided tuple contains " ++ std.fmt.comptimePrint("{d}", .{extra_args.len}));
1125 }
1126
1127 // Setup the tuple that will actually be used with @call (we'll need to insert
1128 // the failing allocator in field @"0" before each @call)
1129 var args: ArgsTuple = undefined;
1130 inline for (@typeInfo(@TypeOf(extra_args)).@"struct".fields, 0..) |field, i| {
1131 const arg_i_str = comptime str: {
1132 var str_buf: [100]u8 = undefined;
1133 const args_i = i + 1;
1134 const str_len = std.fmt.printInt(&str_buf, args_i, 10, .lower, .{});
1135 break :str str_buf[0..str_len];
1136 };
1137 @field(args, arg_i_str) = @field(extra_args, field.name);
1138 }
1139
1140 // Try it once with unlimited memory, make sure it works
1141 const needed_alloc_count = x: {
1142 var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{});
1143 args.@"0" = failing_allocator_inst.allocator();
1144
1145 try @call(.auto, test_fn, args);
1146 break :x failing_allocator_inst.alloc_index;
1147 };
1148
1149 var fail_index: usize = 0;
1150 while (fail_index < needed_alloc_count) : (fail_index += 1) {
1151 var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ .fail_index = fail_index });
1152 args.@"0" = failing_allocator_inst.allocator();
1153
1154 if (@call(.auto, test_fn, args)) |_| {
1155 if (failing_allocator_inst.has_induced_failure) {
1156 return error.SwallowedOutOfMemoryError;
1157 } else {
1158 return error.NondeterministicMemoryUsage;
1159 }
1160 } else |err| switch (err) {
1161 error.OutOfMemory => {
1162 if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {
1163 const tty_config = std.Io.tty.detectConfig(.stderr());
1164 print(
1165 "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}",
1166 .{
1167 fail_index,
1168 needed_alloc_count,
1169 failing_allocator_inst.allocated_bytes,
1170 failing_allocator_inst.freed_bytes,
1171 failing_allocator_inst.allocations,
1172 failing_allocator_inst.deallocations,
1173 std.debug.FormatStackTrace{
1174 .stack_trace = failing_allocator_inst.getStackTrace(),
1175 .tty_config = tty_config,
1176 },
1177 },
1178 );
1179 return error.MemoryLeakDetected;
1180 }
1181 },
1182 else => return err,
1183 }
1184 }
1185}
1186
1187/// Given a type, references all the declarations inside, so that the semantic analyzer sees them.
1188pub fn refAllDecls(comptime T: type) void {
1189 if (!builtin.is_test) return;
1190 inline for (comptime std.meta.declarations(T)) |decl| {
1191 _ = &@field(T, decl.name);
1192 }
1193}
1194
1195/// Given a type, recursively references all the declarations inside, so that the semantic analyzer sees them.
1196/// For deep types, you may use `@setEvalBranchQuota`.
1197pub fn refAllDeclsRecursive(comptime T: type) void {
1198 if (!builtin.is_test) return;
1199 inline for (comptime std.meta.declarations(T)) |decl| {
1200 if (@TypeOf(@field(T, decl.name)) == type) {
1201 switch (@typeInfo(@field(T, decl.name))) {
1202 .@"struct", .@"enum", .@"union", .@"opaque" => refAllDeclsRecursive(@field(T, decl.name)),
1203 else => {},
1204 }
1205 }
1206 _ = &@field(T, decl.name);
1207 }
1208}
1209
1210pub const FuzzInputOptions = struct {
1211 corpus: []const []const u8 = &.{},
1212};
1213
1214/// Inline to avoid coverage instrumentation.
1215pub inline fn fuzz(
1216 context: anytype,
1217 comptime testOne: fn (context: @TypeOf(context), input: []const u8) anyerror!void,
1218 options: FuzzInputOptions,
1219) anyerror!void {
1220 return @import("root").fuzz(context, testOne, options);
1221}
1222
1223/// A `std.Io.Reader` that writes a predetermined list of buffers during `stream`.
1224pub const Reader = struct {
1225 calls: []const Call,
1226 interface: std.Io.Reader,
1227 next_call_index: usize,
1228 next_offset: usize,
1229 /// Further reduces how many bytes are written in each `stream` call.
1230 artificial_limit: std.Io.Limit = .unlimited,
1231
1232 pub const Call = struct {
1233 buffer: []const u8,
1234 };
1235
1236 pub fn init(buffer: []u8, calls: []const Call) Reader {
1237 return .{
1238 .next_call_index = 0,
1239 .next_offset = 0,
1240 .interface = .{
1241 .vtable = &.{ .stream = stream },
1242 .buffer = buffer,
1243 .seek = 0,
1244 .end = 0,
1245 },
1246 .calls = calls,
1247 };
1248 }
1249
1250 fn stream(io_r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
1251 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
1252 if (r.calls.len - r.next_call_index == 0) return error.EndOfStream;
1253 const call = r.calls[r.next_call_index];
1254 const buffer = r.artificial_limit.sliceConst(limit.sliceConst(call.buffer[r.next_offset..]));
1255 const n = try w.write(buffer);
1256 r.next_offset += n;
1257 if (call.buffer.len - r.next_offset == 0) {
1258 r.next_call_index += 1;
1259 r.next_offset = 0;
1260 }
1261 return n;
1262 }
1263};
1264
1265/// A `std.Io.Reader` that gets its data from another `std.Io.Reader`, and always
1266/// writes to its own buffer (and returns 0) during `stream` and `readVec`.
1267pub const ReaderIndirect = struct {
1268 in: *std.Io.Reader,
1269 interface: std.Io.Reader,
1270
1271 pub fn init(in: *std.Io.Reader, buffer: []u8) ReaderIndirect {
1272 return .{
1273 .in = in,
1274 .interface = .{
1275 .vtable = &.{
1276 .stream = stream,
1277 .readVec = readVec,
1278 },
1279 .buffer = buffer,
1280 .seek = 0,
1281 .end = 0,
1282 },
1283 };
1284 }
1285
1286 fn readVec(r: *std.Io.Reader, _: [][]u8) std.Io.Reader.Error!usize {
1287 try streamInner(r);
1288 return 0;
1289 }
1290
1291 fn stream(r: *std.Io.Reader, _: *std.Io.Writer, _: std.Io.Limit) std.Io.Reader.StreamError!usize {
1292 try streamInner(r);
1293 return 0;
1294 }
1295
1296 fn streamInner(r: *std.Io.Reader) std.Io.Reader.Error!void {
1297 const r_indirect: *ReaderIndirect = @alignCast(@fieldParentPtr("interface", r));
1298
1299 // If there's no room remaining in the buffer at all, make room.
1300 if (r.buffer.len == r.end) {
1301 try r.rebase(r.buffer.len);
1302 }
1303
1304 var writer: std.Io.Writer = .{
1305 .buffer = r.buffer,
1306 .end = r.end,
1307 .vtable = &.{
1308 .drain = std.Io.Writer.unreachableDrain,
1309 .rebase = std.Io.Writer.unreachableRebase,
1310 },
1311 };
1312 defer r.end = writer.end;
1313
1314 r_indirect.in.streamExact(&writer, r.buffer.len - r.end) catch |err| switch (err) {
1315 // Only forward EndOfStream if no new bytes were written to the buffer
1316 error.EndOfStream => |e| if (r.end == writer.end) {
1317 return e;
1318 },
1319 error.WriteFailed => unreachable,
1320 else => |e| return e,
1321 };
1322 }
1323};