authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2022-06-23 17:20:24-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2022-06-23 17:20:24-07:00
logc321b2f2a0257fa45262f1ece0a263b22e8c70f3
treecf0a1ada1de0b1cf7a3d4a69f784f052c33d3ef2
parentdef304a9a5b9d001e37c19509a9d2b2450088033

checkAllAllocationFailures: add possibility of SwallowedOutOfMemoryError (split from NondeterministicMemoryUsage)

Inducing failure but not getting OutOfMemory back is not as much of a problem as never inducing failure when it was expected to be induced, so treating them differently and allowing them to be handled differently by the caller is useful. For example, the current implementation of `std.HashMapUnmanaged.getOrPutContextAdapted` always tries to grow and then recovers from OutOfMemory by attempting a lookup of an existing key. If this function is used (i.e. from `std.BufMap.putMove`) with `checkAllAllocationFailures`, then we'd have previously triggered `error.NondeterministicMemoryUsage`, but the real cause is that `OutOfMemory` is being recovered from and so the error is being swallowed. The new error allows us to both understand what's happening easier and to catch it and ignore it if we're okay with the code we're testing handling `error.OutOfMemory` without always bubbling it up.

1 files changed, 18 insertions(+), 5 deletions(-)

lib/std/testing.zig+18-5
...@@ -534,18 +534,27 @@ test {...@@ -534,18 +534,27 @@ test {
534///534///
535/// Any relevant state shared between runs of `test_fn` *must* be reset within `test_fn`.535/// Any relevant state shared between runs of `test_fn` *must* be reset within `test_fn`.
536///536///
537/// Expects that the `test_fn` has a deterministic number of memory allocations
538/// (an error will be returned if non-deterministic allocations are detected).
539///
540/// The strategy employed is to:537/// The strategy employed is to:
541/// - Run the test function once to get the total number of allocations.538/// - Run the test function once to get the total number of allocations.
542/// - Then, iterate and run the function X more times, incrementing539/// - Then, iterate and run the function X more times, incrementing
543/// the failing index each iteration (where X is the total number of540/// the failing index each iteration (where X is the total number of
544/// allocations determined previously)541/// allocations determined previously)
545///542///
543/// Expects that `test_fn` has a deterministic number of memory allocations:
544/// - If an allocation was made to fail during a run of `test_fn`, but `test_fn`
545/// didn't return `error.OutOfMemory`, then `error.SwallowedOutOfMemoryError`
546/// is returned from `checkAllAllocationFailures`. You may want to ignore this
547/// depending on whether or not the code you're testing includes some strategies
548/// for recovering from `error.OutOfMemory`.
549/// - If a run of `test_fn` with an expected allocation failure executes without
550/// an allocation failure being induced, then `error.NondeterministicMemoryUsage`
551/// is returned. This error means that there are allocation points that won't be
552/// tested by the strategy this function employs (that is, there are sometimes more
553/// points of allocation than the initial run of `test_fn` detects).
554///
546/// ---555/// ---
547///556///
548/// Here's an example of using a simple test case that will cause a leak when the557/// Here's an example using a simple test case that will cause a leak when the
549/// allocation of `bar` fails (but will pass normally):558/// allocation of `bar` fails (but will pass normally):
550///559///
551/// ```zig560/// ```zig
...@@ -645,7 +654,11 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime...@@ -645,7 +654,11 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
645 args.@"0" = failing_allocator_inst.allocator();654 args.@"0" = failing_allocator_inst.allocator();
646655
647 if (@call(.{}, test_fn, args)) |_| {656 if (@call(.{}, test_fn, args)) |_| {
648 return error.NondeterministicMemoryUsage;657 if (failing_allocator_inst.has_induced_failure) {
658 return error.SwallowedOutOfMemoryError;
659 } else {
660 return error.NondeterministicMemoryUsage;
661 }
649 } else |err| switch (err) {662 } else |err| switch (err) {
650 error.OutOfMemory => {663 error.OutOfMemory => {
651 if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {664 if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {