fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime test_fn: anytype, extra_args: anytype) !void
Exhaustively check that allocation failures within test_fn
are handled without introducing memory leaks. If used with the testing.allocator
as the backing_allocator
, it will also be able to detect double frees, etc (when runtime safety is enabled).
The provided test_fn
must have a std.mem.Allocator
as its first argument, and must have a return type of !void
. Any extra arguments of test_fn
can be provided via the extra_args
tuple.
Any relevant state shared between runs of test_fn
must be reset within test_fn
.
The strategy employed is to:
- Run the test function once to get the total number of allocations.
- Then, iterate and run the function X more times, incrementing the failing index each iteration (where X is the total number of allocations determined previously)
Expects that test_fn
has a deterministic number of memory allocations:
- If an allocation was made to fail during a run of
test_fn
, buttest_fn
didn’t returnerror.OutOfMemory
, thenerror.SwallowedOutOfMemoryError
is returned fromcheckAllAllocationFailures
. You may want to ignore this depending on whether or not the code you’re testing includes some strategies for recovering fromerror.OutOfMemory
. - If a run of
test_fn
with an expected allocation failure executes without an allocation failure being induced, thenerror.NondeterministicMemoryUsage
is returned. This error means that there are allocation points that won’t be tested by the strategy this function employs (that is, there are sometimes more points of allocation than the initial run oftest_fn
detects).
Here’s an example using a simple test case that will cause a leak when the allocation of bar
fails (but will pass normally):
test {
const length: usize = 10;
const allocator = std.testing.allocator;
var foo = try allocator.alloc(u8, length);
var bar = try allocator.alloc(u8, length);
allocator.free(foo);
allocator.free(bar);
}
The test case can be converted to something that this function can use by doing:
fn testImpl(allocator: std.mem.Allocator, length: usize) !void {
var foo = try allocator.alloc(u8, length);
var bar = try allocator.alloc(u8, length);
allocator.free(foo);
allocator.free(bar);
}
test {
const length: usize = 10;
const allocator = std.testing.allocator;
try std.testing.checkAllAllocationFailures(allocator, testImpl, .{length});
}
Running this test will show that foo
is leaked when the allocation of bar
fails. The simplest fix, in this case, would be to use defer like so:
fn testImpl(allocator: std.mem.Allocator, length: usize) !void {
var foo = try allocator.alloc(u8, length);
defer allocator.free(foo);
var bar = try allocator.alloc(u8, length);
defer allocator.free(bar);
}