diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index ec38fad50b..a258475517 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -1587,13 +1587,8 @@ test "std.ArrayListUnmanaged(u8) implements writer" { } test "shrink still sets length when resizing is disabled" { - // Use the testing allocator but with resize disabled. - var a = testing.allocator; - a.vtable = &.{ - .alloc = a.vtable.alloc, - .resize = Allocator.noResize, - .free = a.vtable.free, - }; + var failing_allocator = testing.FailingAllocator.init(testing.allocator, .{ .resize_fail_index = 0 }); + const a = failing_allocator.allocator(); { var list = ArrayList(i32).init(a); @@ -1620,13 +1615,9 @@ test "shrink still sets length when resizing is disabled" { } test "shrinkAndFree with a copy" { - // Use the testing allocator but with resize disabled. - var a = testing.allocator; - a.vtable = &.{ - .alloc = a.vtable.alloc, - .resize = Allocator.noResize, - .free = a.vtable.free, - }; + var failing_allocator = testing.FailingAllocator.init(testing.allocator, .{ .resize_fail_index = 0 }); + const a = failing_allocator.allocator(); + var list = ArrayList(i32).init(a); defer list.deinit(); @@ -1748,8 +1739,7 @@ test "ArrayListAligned/ArrayListAlignedUnmanaged accepts unaligned slices" { test "std.ArrayList(u0)" { // An ArrayList on zero-sized types should not need to allocate - var failing_allocator = testing.FailingAllocator.init(testing.allocator, 0); - const a = failing_allocator.allocator(); + const a = testing.failing_allocator; var list = ArrayList(u0).init(a); defer list.deinit(); diff --git a/lib/std/heap/general_purpose_allocator.zig b/lib/std/heap/general_purpose_allocator.zig index ccc4b6a8b2..7bdcf9ca2d 100644 --- a/lib/std/heap/general_purpose_allocator.zig +++ b/lib/std/heap/general_purpose_allocator.zig @@ -1305,7 +1305,7 @@ test "realloc large object to larger alignment" { } test "large object shrinks to small but allocation fails during shrink" { - var failing_allocator = std.testing.FailingAllocator.init(std.heap.page_allocator, 3); + var failing_allocator = std.testing.FailingAllocator.init(std.heap.page_allocator, .{ .fail_index = 3 }); var gpa = GeneralPurposeAllocator(.{}){ .backing_allocator = failing_allocator.allocator() }; defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak"); const allocator = gpa.allocator(); diff --git a/lib/std/heap/memory_pool.zig b/lib/std/heap/memory_pool.zig index b56a15d006..764198d36d 100644 --- a/lib/std/heap/memory_pool.zig +++ b/lib/std/heap/memory_pool.zig @@ -164,8 +164,8 @@ test "memory pool: preheating (success)" { } test "memory pool: preheating (failure)" { - var failer = std.testing.FailingAllocator.init(std.testing.allocator, 0); - try std.testing.expectError(error.OutOfMemory, MemoryPool(u32).initPreheated(failer.allocator(), 5)); + var failer = std.testing.failing_allocator; + try std.testing.expectError(error.OutOfMemory, MemoryPool(u32).initPreheated(failer, 5)); } test "memory pool: growable" { diff --git a/lib/std/json/scanner_test.zig b/lib/std/json/scanner_test.zig index acc4fc7404..8ab7f5e715 100644 --- a/lib/std/json/scanner_test.zig +++ b/lib/std/json/scanner_test.zig @@ -397,7 +397,7 @@ test "skipValue" { } fn testEnsureStackCapacity(do_ensure: bool) !void { - var fail_alloc = std.testing.FailingAllocator.init(std.testing.allocator, 1); + var fail_alloc = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 1 }); const failing_allocator = fail_alloc.allocator(); const nestings = 999; // intentionally not a power of 2. diff --git a/lib/std/json/static_test.zig b/lib/std/json/static_test.zig index bbfdc6c409..82b0d89044 100644 --- a/lib/std/json/static_test.zig +++ b/lib/std/json/static_test.zig @@ -461,8 +461,7 @@ test "parse into tagged union errors" { try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"nothing\":{\"no\":0}}", .{})); // Allocator failure - var fail_alloc = testing.FailingAllocator.init(testing.allocator, 0); - try testing.expectError(error.OutOfMemory, parseFromSlice(T, fail_alloc.allocator(), "{\"string\"\"foo\"}", .{})); + try testing.expectError(error.OutOfMemory, parseFromSlice(T, testing.failing_allocator, "{\"string\"\"foo\"}", .{})); } test "parse into struct with no fields" { diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 40a95a450b..17ac0d4b04 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -14,7 +14,7 @@ pub var allocator_instance = b: { }; pub const failing_allocator = failing_allocator_instance.allocator(); -pub var failing_allocator_instance = FailingAllocator.init(base_allocator_instance.allocator(), 0); +pub var failing_allocator_instance = FailingAllocator.init(base_allocator_instance.allocator(), .{ .fail_index = 0 }); pub var base_allocator_instance = std.heap.FixedBufferAllocator.init(""); @@ -1081,16 +1081,16 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime // 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, std.math.maxInt(usize)); + var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{}); args.@"0" = failing_allocator_inst.allocator(); try @call(.auto, test_fn, args); - break :x failing_allocator_inst.index; + break :x failing_allocator_inst.alloc_index; }; var fail_index: usize = 0; while (fail_index < needed_alloc_count) : (fail_index += 1) { - var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, fail_index); + 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)) |_| { diff --git a/lib/std/testing/failing_allocator.zig b/lib/std/testing/failing_allocator.zig index 313af987ab..3a83f313bf 100644 --- a/lib/std/testing/failing_allocator.zig +++ b/lib/std/testing/failing_allocator.zig @@ -1,19 +1,33 @@ const std = @import("../std.zig"); const mem = std.mem; +pub const Config = struct { + /// The number of successful allocations you can expect from this allocator. + /// The next allocation will fail. For example, with `fail_index` equal to + /// 2, the following test will pass: + /// + /// var a = try failing_alloc.create(i32); + /// var b = try failing_alloc.create(i32); + /// testing.expectError(error.OutOfMemory, failing_alloc.create(i32)); + fail_index: usize = std.math.maxInt(usize), + + /// Number of successful resizes to expect from this allocator. The next resize will fail. + resize_fail_index: usize = std.math.maxInt(usize), +}; + /// Allocator that fails after N allocations, useful for making sure out of /// memory conditions are handled correctly. /// /// To use this, first initialize it and get an allocator with /// /// `const failing_allocator = &FailingAllocator.init(, -/// ).allocator;` +/// ).allocator;` /// /// Then use `failing_allocator` anywhere you would have used a /// different allocator. pub const FailingAllocator = struct { - index: usize, - fail_index: usize, + alloc_index: usize, + resize_index: usize, internal_allocator: mem.Allocator, allocated_bytes: usize, freed_bytes: usize, @@ -21,28 +35,24 @@ pub const FailingAllocator = struct { deallocations: usize, stack_addresses: [num_stack_frames]usize, has_induced_failure: bool, + fail_index: usize, + resize_fail_index: usize, const num_stack_frames = if (std.debug.sys_can_stack_trace) 16 else 0; - /// `fail_index` is the number of successful allocations you can - /// expect from this allocator. The next allocation will fail. - /// For example, if this is called with `fail_index` equal to 2, - /// the following test will pass: - /// - /// var a = try failing_alloc.create(i32); - /// var b = try failing_alloc.create(i32); - /// testing.expectError(error.OutOfMemory, failing_alloc.create(i32)); - pub fn init(internal_allocator: mem.Allocator, fail_index: usize) FailingAllocator { + pub fn init(internal_allocator: mem.Allocator, config: Config) FailingAllocator { return FailingAllocator{ .internal_allocator = internal_allocator, - .fail_index = fail_index, - .index = 0, + .alloc_index = 0, + .resize_index = 0, .allocated_bytes = 0, .freed_bytes = 0, .allocations = 0, .deallocations = 0, .stack_addresses = undefined, .has_induced_failure = false, + .fail_index = config.fail_index, + .resize_fail_index = config.resize_fail_index, }; } @@ -64,7 +74,7 @@ pub const FailingAllocator = struct { return_address: usize, ) ?[*]u8 { const self: *FailingAllocator = @ptrCast(@alignCast(ctx)); - if (self.index == self.fail_index) { + if (self.alloc_index == self.fail_index) { if (!self.has_induced_failure) { @memset(&self.stack_addresses, 0); var stack_trace = std.builtin.StackTrace{ @@ -80,7 +90,7 @@ pub const FailingAllocator = struct { return null; self.allocated_bytes += len; self.allocations += 1; - self.index += 1; + self.alloc_index += 1; return result; } @@ -92,6 +102,8 @@ pub const FailingAllocator = struct { ra: usize, ) bool { const self: *FailingAllocator = @ptrCast(@alignCast(ctx)); + if (self.resize_index == self.resize_fail_index) + return false; if (!self.internal_allocator.rawResize(old_mem, log2_old_align, new_len, ra)) return false; if (new_len < old_mem.len) { @@ -99,6 +111,7 @@ pub const FailingAllocator = struct { } else { self.allocated_bytes += new_len - old_mem.len; } + self.resize_index += 1; return true; }