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