mirror of
https://github.com/ziglang/zig.git
synced 2025-12-09 07:43:10 +00:00
std: enable FailingAllocator to fail on resize
Now that allocator.resize() is allowed to fail, programs may wish to test code paths that handle resize() failure. The simplest way to do this now is to replace the vtable of the testing allocator with one that uses Allocator.noResize for the 'resize' function pointer. An alternative way to support this testing capability is to augment the FailingAllocator (which is already useful for testing allocation failure scenarios) to intentionally fail on calls to resize(). To do this, add a 'resize_fail_index' parameter to the FailingAllocator that causes resize() to fail after the given number of calls.
This commit is contained in:
parent
8976ad7ecb
commit
cab9da35bd
@ -1587,13 +1587,8 @@ test "std.ArrayListUnmanaged(u8) implements writer" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "shrink still sets length when resizing is disabled" {
|
test "shrink still sets length when resizing is disabled" {
|
||||||
// Use the testing allocator but with resize disabled.
|
var failing_allocator = testing.FailingAllocator.init(testing.allocator, .{ .resize_fail_index = 0 });
|
||||||
var a = testing.allocator;
|
const a = failing_allocator.allocator();
|
||||||
a.vtable = &.{
|
|
||||||
.alloc = a.vtable.alloc,
|
|
||||||
.resize = Allocator.noResize,
|
|
||||||
.free = a.vtable.free,
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
{
|
||||||
var list = ArrayList(i32).init(a);
|
var list = ArrayList(i32).init(a);
|
||||||
@ -1620,13 +1615,9 @@ test "shrink still sets length when resizing is disabled" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "shrinkAndFree with a copy" {
|
test "shrinkAndFree with a copy" {
|
||||||
// Use the testing allocator but with resize disabled.
|
var failing_allocator = testing.FailingAllocator.init(testing.allocator, .{ .resize_fail_index = 0 });
|
||||||
var a = testing.allocator;
|
const a = failing_allocator.allocator();
|
||||||
a.vtable = &.{
|
|
||||||
.alloc = a.vtable.alloc,
|
|
||||||
.resize = Allocator.noResize,
|
|
||||||
.free = a.vtable.free,
|
|
||||||
};
|
|
||||||
var list = ArrayList(i32).init(a);
|
var list = ArrayList(i32).init(a);
|
||||||
defer list.deinit();
|
defer list.deinit();
|
||||||
|
|
||||||
@ -1748,8 +1739,7 @@ test "ArrayListAligned/ArrayListAlignedUnmanaged accepts unaligned slices" {
|
|||||||
|
|
||||||
test "std.ArrayList(u0)" {
|
test "std.ArrayList(u0)" {
|
||||||
// An ArrayList on zero-sized types should not need to allocate
|
// An ArrayList on zero-sized types should not need to allocate
|
||||||
var failing_allocator = testing.FailingAllocator.init(testing.allocator, 0);
|
const a = testing.failing_allocator;
|
||||||
const a = failing_allocator.allocator();
|
|
||||||
|
|
||||||
var list = ArrayList(u0).init(a);
|
var list = ArrayList(u0).init(a);
|
||||||
defer list.deinit();
|
defer list.deinit();
|
||||||
|
|||||||
@ -1305,7 +1305,7 @@ test "realloc large object to larger alignment" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "large object shrinks to small but allocation fails during shrink" {
|
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() };
|
var gpa = GeneralPurposeAllocator(.{}){ .backing_allocator = failing_allocator.allocator() };
|
||||||
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
|
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|||||||
@ -164,8 +164,8 @@ test "memory pool: preheating (success)" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "memory pool: preheating (failure)" {
|
test "memory pool: preheating (failure)" {
|
||||||
var failer = std.testing.FailingAllocator.init(std.testing.allocator, 0);
|
var failer = std.testing.failing_allocator;
|
||||||
try std.testing.expectError(error.OutOfMemory, MemoryPool(u32).initPreheated(failer.allocator(), 5));
|
try std.testing.expectError(error.OutOfMemory, MemoryPool(u32).initPreheated(failer, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "memory pool: growable" {
|
test "memory pool: growable" {
|
||||||
|
|||||||
@ -397,7 +397,7 @@ test "skipValue" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn testEnsureStackCapacity(do_ensure: bool) !void {
|
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 failing_allocator = fail_alloc.allocator();
|
||||||
|
|
||||||
const nestings = 999; // intentionally not a power of 2.
|
const nestings = 999; // intentionally not a power of 2.
|
||||||
|
|||||||
@ -461,8 +461,7 @@ test "parse into tagged union errors" {
|
|||||||
try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"nothing\":{\"no\":0}}", .{}));
|
try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"nothing\":{\"no\":0}}", .{}));
|
||||||
|
|
||||||
// Allocator failure
|
// Allocator failure
|
||||||
var fail_alloc = testing.FailingAllocator.init(testing.allocator, 0);
|
try testing.expectError(error.OutOfMemory, parseFromSlice(T, testing.failing_allocator, "{\"string\"\"foo\"}", .{}));
|
||||||
try testing.expectError(error.OutOfMemory, parseFromSlice(T, fail_alloc.allocator(), "{\"string\"\"foo\"}", .{}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse into struct with no fields" {
|
test "parse into struct with no fields" {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ pub var allocator_instance = b: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const failing_allocator = failing_allocator_instance.allocator();
|
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("");
|
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
|
// Try it once with unlimited memory, make sure it works
|
||||||
const needed_alloc_count = x: {
|
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();
|
args.@"0" = failing_allocator_inst.allocator();
|
||||||
|
|
||||||
try @call(.auto, test_fn, args);
|
try @call(.auto, test_fn, args);
|
||||||
break :x failing_allocator_inst.index;
|
break :x failing_allocator_inst.alloc_index;
|
||||||
};
|
};
|
||||||
|
|
||||||
var fail_index: usize = 0;
|
var fail_index: usize = 0;
|
||||||
while (fail_index < needed_alloc_count) : (fail_index += 1) {
|
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();
|
args.@"0" = failing_allocator_inst.allocator();
|
||||||
|
|
||||||
if (@call(.auto, test_fn, args)) |_| {
|
if (@call(.auto, test_fn, args)) |_| {
|
||||||
|
|||||||
@ -1,19 +1,33 @@
|
|||||||
const std = @import("../std.zig");
|
const std = @import("../std.zig");
|
||||||
const mem = std.mem;
|
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
|
/// Allocator that fails after N allocations, useful for making sure out of
|
||||||
/// memory conditions are handled correctly.
|
/// memory conditions are handled correctly.
|
||||||
///
|
///
|
||||||
/// To use this, first initialize it and get an allocator with
|
/// To use this, first initialize it and get an allocator with
|
||||||
///
|
///
|
||||||
/// `const failing_allocator = &FailingAllocator.init(<allocator>,
|
/// `const failing_allocator = &FailingAllocator.init(<allocator>,
|
||||||
/// <fail_index>).allocator;`
|
/// <config>).allocator;`
|
||||||
///
|
///
|
||||||
/// Then use `failing_allocator` anywhere you would have used a
|
/// Then use `failing_allocator` anywhere you would have used a
|
||||||
/// different allocator.
|
/// different allocator.
|
||||||
pub const FailingAllocator = struct {
|
pub const FailingAllocator = struct {
|
||||||
index: usize,
|
alloc_index: usize,
|
||||||
fail_index: usize,
|
resize_index: usize,
|
||||||
internal_allocator: mem.Allocator,
|
internal_allocator: mem.Allocator,
|
||||||
allocated_bytes: usize,
|
allocated_bytes: usize,
|
||||||
freed_bytes: usize,
|
freed_bytes: usize,
|
||||||
@ -21,28 +35,24 @@ pub const FailingAllocator = struct {
|
|||||||
deallocations: usize,
|
deallocations: usize,
|
||||||
stack_addresses: [num_stack_frames]usize,
|
stack_addresses: [num_stack_frames]usize,
|
||||||
has_induced_failure: bool,
|
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;
|
const num_stack_frames = if (std.debug.sys_can_stack_trace) 16 else 0;
|
||||||
|
|
||||||
/// `fail_index` is the number of successful allocations you can
|
pub fn init(internal_allocator: mem.Allocator, config: Config) FailingAllocator {
|
||||||
/// 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 {
|
|
||||||
return FailingAllocator{
|
return FailingAllocator{
|
||||||
.internal_allocator = internal_allocator,
|
.internal_allocator = internal_allocator,
|
||||||
.fail_index = fail_index,
|
.alloc_index = 0,
|
||||||
.index = 0,
|
.resize_index = 0,
|
||||||
.allocated_bytes = 0,
|
.allocated_bytes = 0,
|
||||||
.freed_bytes = 0,
|
.freed_bytes = 0,
|
||||||
.allocations = 0,
|
.allocations = 0,
|
||||||
.deallocations = 0,
|
.deallocations = 0,
|
||||||
.stack_addresses = undefined,
|
.stack_addresses = undefined,
|
||||||
.has_induced_failure = false,
|
.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,
|
return_address: usize,
|
||||||
) ?[*]u8 {
|
) ?[*]u8 {
|
||||||
const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
|
const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
|
||||||
if (self.index == self.fail_index) {
|
if (self.alloc_index == self.fail_index) {
|
||||||
if (!self.has_induced_failure) {
|
if (!self.has_induced_failure) {
|
||||||
@memset(&self.stack_addresses, 0);
|
@memset(&self.stack_addresses, 0);
|
||||||
var stack_trace = std.builtin.StackTrace{
|
var stack_trace = std.builtin.StackTrace{
|
||||||
@ -80,7 +90,7 @@ pub const FailingAllocator = struct {
|
|||||||
return null;
|
return null;
|
||||||
self.allocated_bytes += len;
|
self.allocated_bytes += len;
|
||||||
self.allocations += 1;
|
self.allocations += 1;
|
||||||
self.index += 1;
|
self.alloc_index += 1;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +102,8 @@ pub const FailingAllocator = struct {
|
|||||||
ra: usize,
|
ra: usize,
|
||||||
) bool {
|
) bool {
|
||||||
const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
|
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))
|
if (!self.internal_allocator.rawResize(old_mem, log2_old_align, new_len, ra))
|
||||||
return false;
|
return false;
|
||||||
if (new_len < old_mem.len) {
|
if (new_len < old_mem.len) {
|
||||||
@ -99,6 +111,7 @@ pub const FailingAllocator = struct {
|
|||||||
} else {
|
} else {
|
||||||
self.allocated_bytes += new_len - old_mem.len;
|
self.allocated_bytes += new_len - old_mem.len;
|
||||||
}
|
}
|
||||||
|
self.resize_index += 1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user