| ... | ... | @@ -1112,48 +1112,16 @@ test { |
| 1112 | 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 { |
| 1116 | | switch (@typeInfo(@typeInfo(@TypeOf(test_fn)).@"fn".return_type.?)) { |
| 1117 | | .error_union => |info| { |
| 1118 | | if (info.payload != void) { |
| 1119 | | @compileError("Return type must be !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 | | |
| 1115 | pub fn checkAllAllocationFailures( |
| 1116 | backing_allocator: std.mem.Allocator, |
| 1117 | comptime test_fn: anytype, |
| 1118 | extra_args: CheckAllAllocationFailuresExtraArgs(@TypeOf(test_fn)), |
| 1119 | ) !void { |
| 1151 | 1120 | // Try it once with unlimited memory, make sure it works |
| 1152 | 1121 | const needed_alloc_count = x: { |
| 1153 | 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 | 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 | 1129 | var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ |
| 1162 | 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 | 1134 | if (failing_allocator_inst.has_induced_failure) { |
| 1168 | 1135 | return error.SwallowedOutOfMemoryError; |
| 1169 | 1136 | } else { |
| ... | ... | @@ -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 | 1212 | /// Given a type, references all the declarations inside, so that the semantic analyzer sees them. |
| 1198 | 1213 | pub fn refAllDecls(comptime T: type) void { |
| 1199 | 1214 | if (!builtin.is_test) return; |