| ... | @@ -1112,48 +1112,16 @@ test { | ... | @@ -1112,48 +1112,16 @@ test { |
| 1112 | /// defer allocator.free(bar); | 1112 | /// defer allocator.free(bar); |
| 1113 | /// } | 1113 | /// } |
| 1114 | /// ``` | 1114 | /// ``` |
| 1115 | pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime test_fn: anytype, extra_args: anytype) !void { | 1115 | pub fn checkAllAllocationFailures( |
| 1116 | switch (@typeInfo(@typeInfo(@TypeOf(test_fn)).@"fn".return_type.?)) { | 1116 | backing_allocator: std.mem.Allocator, |
| 1117 | .error_union => |info| { | 1117 | comptime test_fn: anytype, |
| 1118 | if (info.payload != void) { | 1118 | extra_args: CheckAllAllocationFailuresExtraArgs(@TypeOf(test_fn)), |
| 1119 | @compileError("Return type must be !void"); | 1119 | ) !void { |
| 1120 | } | | |
| 1121 | }, | | |
| 1122 | else => @compileError("Return type must be !void"), | | |
| 1123 | } | | |
| 1124 | if (@typeInfo(@TypeOf(extra_args)) != .@"struct") { | | |
| 1125 | @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(extra_args))); | | |
| 1126 | } | | |
| 1127 | | | |
| 1128 | const ArgsTuple = std.meta.ArgsTuple(@TypeOf(test_fn)); | | |
| 1129 | const fn_args_fields = @typeInfo(ArgsTuple).@"struct".fields; | | |
| 1130 | if (fn_args_fields.len == 0 or fn_args_fields[0].type != std.mem.Allocator) { | | |
| 1131 | @compileError("The provided function must have an " ++ @typeName(std.mem.Allocator) ++ " as its first argument"); | | |
| 1132 | } | | |
| 1133 | const expected_args_tuple_len = fn_args_fields.len - 1; | | |
| 1134 | if (extra_args.len != expected_args_tuple_len) { | | |
| 1135 | @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})); | | |
| 1136 | } | | |
| 1137 | | | |
| 1138 | // Setup the tuple that will actually be used with @call (we'll need to insert | | |
| 1139 | // the failing allocator in field @"0" before each @call) | | |
| 1140 | var args: ArgsTuple = undefined; | | |
| 1141 | inline for (@typeInfo(@TypeOf(extra_args)).@"struct".fields, 0..) |field, i| { | | |
| 1142 | const arg_i_str = comptime str: { | | |
| 1143 | var str_buf: [100]u8 = undefined; | | |
| 1144 | const args_i = i + 1; | | |
| 1145 | const str_len = std.fmt.printInt(&str_buf, args_i, 10, .lower, .{}); | | |
| 1146 | break :str str_buf[0..str_len]; | | |
| 1147 | }; | | |
| 1148 | @field(args, arg_i_str) = @field(extra_args, field.name); | | |
| 1149 | } | | |
| 1150 | | | |
| 1151 | // Try it once with unlimited memory, make sure it works | 1120 | // Try it once with unlimited memory, make sure it works |
| 1152 | const needed_alloc_count = x: { | 1121 | const needed_alloc_count = x: { |
| 1153 | var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{}); | 1122 | var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{}); |
| 1154 | args.@"0" = failing_allocator_inst.allocator(); | | |
| 1155 | | 1123 | |
| 1156 | try @call(.auto, test_fn, args); | 1124 | try @call(.auto, test_fn, .{failing_allocator_inst.allocator()} ++ extra_args); |
| 1157 | break :x failing_allocator_inst.alloc_index; | 1125 | break :x failing_allocator_inst.alloc_index; |
| 1158 | }; | 1126 | }; |
| 1159 | | 1127 | |
| ... | @@ -1161,9 +1129,8 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime | ... | @@ -1161,9 +1129,8 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime |
| 1161 | var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ | 1129 | var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ |
| 1162 | .fail_index = fail_index, | 1130 | .fail_index = fail_index, |
| 1163 | }); | 1131 | }); |
| 1164 | args.@"0" = failing_allocator_inst.allocator(); | | |
| 1165 | | 1132 | |
| 1166 | if (@call(.auto, test_fn, args)) |_| { | 1133 | if (@call(.auto, test_fn, .{failing_allocator_inst.allocator()} ++ extra_args)) |_| { |
| 1167 | if (failing_allocator_inst.has_induced_failure) { | 1134 | if (failing_allocator_inst.has_induced_failure) { |
| 1168 | return error.SwallowedOutOfMemoryError; | 1135 | return error.SwallowedOutOfMemoryError; |
| 1169 | } else { | 1136 | } else { |
| ... | @@ -1194,6 +1161,54 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime | ... | @@ -1194,6 +1161,54 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime |
| 1194 | } | 1161 | } |
| 1195 | } | 1162 | } |
| 1196 | | 1163 | |
| | 1164 | fn CheckAllAllocationFailuresExtraArgs(comptime TestFn: type) type { |
| | 1165 | switch (@typeInfo(@typeInfo(TestFn).@"fn".return_type.?)) { |
| | 1166 | .error_union => |info| { |
| | 1167 | if (info.payload != void) { |
| | 1168 | @compileError("Return type must be !void"); |
| | 1169 | } |
| | 1170 | }, |
| | 1171 | else => @compileError("Return type must be !void"), |
| | 1172 | } |
| | 1173 | |
| | 1174 | const ArgsTuple = std.meta.ArgsTuple(TestFn); |
| | 1175 | |
| | 1176 | const fields = @typeInfo(ArgsTuple).@"struct".fields; |
| | 1177 | if (fields.len == 0 or fields[0].type != std.mem.Allocator) { |
| | 1178 | @compileError("The provided function must have an " ++ @typeName(std.mem.Allocator) ++ " as its first argument"); |
| | 1179 | } |
| | 1180 | |
| | 1181 | var extra_args: [fields.len - 1]type = undefined; |
| | 1182 | for (&extra_args, fields[1..]) |*arg, field| { |
| | 1183 | arg.* = field.type; |
| | 1184 | } |
| | 1185 | |
| | 1186 | return @Tuple(&extra_args); |
| | 1187 | } |
| | 1188 | |
| | 1189 | test "checkAllAllocationFailures provide result type to 'extra_args' argument" { |
| | 1190 | try checkAllAllocationFailures( |
| | 1191 | std.testing.allocator, |
| | 1192 | struct { |
| | 1193 | fn f(ally: std.mem.Allocator, params: struct { |
| | 1194 | foo_len: u32, |
| | 1195 | bar_len: u32, |
| | 1196 | }) !void { |
| | 1197 | const foo = try ally.alloc(u8, params.foo_len); |
| | 1198 | defer ally.free(foo); |
| | 1199 | const bar = try ally.alloc(u8, params.bar_len); |
| | 1200 | defer ally.free(bar); |
| | 1201 | } |
| | 1202 | }.f, |
| | 1203 | .{ |
| | 1204 | .{ |
| | 1205 | .foo_len = 3, |
| | 1206 | .bar_len = 5, |
| | 1207 | }, |
| | 1208 | }, |
| | 1209 | ); |
| | 1210 | } |
| | 1211 | |
| 1197 | /// Given a type, references all the declarations inside, so that the semantic analyzer sees them. | 1212 | /// Given a type, references all the declarations inside, so that the semantic analyzer sees them. |
| 1198 | pub fn refAllDecls(comptime T: type) void { | 1213 | pub fn refAllDecls(comptime T: type) void { |
| 1199 | if (!builtin.is_test) return; | 1214 | if (!builtin.is_test) return; |