diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 29e1fc8d61a7b4809f00ef1af9527d6da659a594..df3cfddb1db4ba93dfa985197d683ab18b27f76c 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1112,48 +1112,16 @@ test { /// defer allocator.free(bar); /// } /// ``` -pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime test_fn: anytype, extra_args: anytype) !void { - switch (@typeInfo(@typeInfo(@TypeOf(test_fn)).@"fn".return_type.?)) { - .error_union => |info| { - if (info.payload != void) { - @compileError("Return type must be !void"); - } - }, - else => @compileError("Return type must be !void"), - } - if (@typeInfo(@TypeOf(extra_args)) != .@"struct") { - @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(extra_args))); - } - - const ArgsTuple = std.meta.ArgsTuple(@TypeOf(test_fn)); - const fn_args_fields = @typeInfo(ArgsTuple).@"struct".fields; - if (fn_args_fields.len == 0 or fn_args_fields[0].type != std.mem.Allocator) { - @compileError("The provided function must have an " ++ @typeName(std.mem.Allocator) ++ " as its first argument"); - } - const expected_args_tuple_len = fn_args_fields.len - 1; - if (extra_args.len != expected_args_tuple_len) { - @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})); - } - - // Setup the tuple that will actually be used with @call (we'll need to insert - // the failing allocator in field @"0" before each @call) - var args: ArgsTuple = undefined; - inline for (@typeInfo(@TypeOf(extra_args)).@"struct".fields, 0..) |field, i| { - const arg_i_str = comptime str: { - var str_buf: [100]u8 = undefined; - const args_i = i + 1; - const str_len = std.fmt.printInt(&str_buf, args_i, 10, .lower, .{}); - break :str str_buf[0..str_len]; - }; - @field(args, arg_i_str) = @field(extra_args, field.name); - } - +pub fn checkAllAllocationFailures( + backing_allocator: std.mem.Allocator, + comptime test_fn: anytype, + extra_args: CheckAllAllocationFailuresExtraArgs(@TypeOf(test_fn)), +) !void { // Try it once with unlimited memory, make sure it works const needed_alloc_count = x: { var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{}); - args.@"0" = failing_allocator_inst.allocator(); - try @call(.auto, test_fn, args); + try @call(.auto, test_fn, .{failing_allocator_inst.allocator()} ++ extra_args); break :x failing_allocator_inst.alloc_index; }; @@ -1161,9 +1129,8 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ .fail_index = fail_index, }); - args.@"0" = failing_allocator_inst.allocator(); - if (@call(.auto, test_fn, args)) |_| { + if (@call(.auto, test_fn, .{failing_allocator_inst.allocator()} ++ extra_args)) |_| { if (failing_allocator_inst.has_induced_failure) { return error.SwallowedOutOfMemoryError; } else { @@ -1194,6 +1161,54 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime } } +fn CheckAllAllocationFailuresExtraArgs(comptime TestFn: type) type { + switch (@typeInfo(@typeInfo(TestFn).@"fn".return_type.?)) { + .error_union => |info| { + if (info.payload != void) { + @compileError("Return type must be !void"); + } + }, + else => @compileError("Return type must be !void"), + } + + const ArgsTuple = std.meta.ArgsTuple(TestFn); + + const fields = @typeInfo(ArgsTuple).@"struct".fields; + if (fields.len == 0 or fields[0].type != std.mem.Allocator) { + @compileError("The provided function must have an " ++ @typeName(std.mem.Allocator) ++ " as its first argument"); + } + + var extra_args: [fields.len - 1]type = undefined; + for (&extra_args, fields[1..]) |*arg, field| { + arg.* = field.type; + } + + return @Tuple(&extra_args); +} + +test "checkAllAllocationFailures provide result type to 'extra_args' argument" { + try checkAllAllocationFailures( + std.testing.allocator, + struct { + fn f(ally: std.mem.Allocator, params: struct { + foo_len: u32, + bar_len: u32, + }) !void { + const foo = try ally.alloc(u8, params.foo_len); + defer ally.free(foo); + const bar = try ally.alloc(u8, params.bar_len); + defer ally.free(bar); + } + }.f, + .{ + .{ + .foo_len = 3, + .bar_len = 5, + }, + }, + ); +} + /// Given a type, references all the declarations inside, so that the semantic analyzer sees them. pub fn refAllDecls(comptime T: type) void { if (!builtin.is_test) return;