Zig 0.17.0-dev (Split by item)

Shortcut keys
?
Show these tips
S
Focus search field
Esc
Clear focus & close tips
U
Jump to source code
↑
Move up in search results
↓
Move down in search results
↵
Go to active search result
This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig standard library. Public Function

checkAllAllocationFailures

Public Function
std.testing.checkAllAllocationFailures
pub fn checkAllAllocationFailures(
    backing_allocator: std.mem.Allocator,
    comptime test_fn: anytype,
    extra_args: CheckAllAllocationFailuresExtraArgs(@TypeOf(test_fn)),
) !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, but test_fn didn't return error.OutOfMemory, then error.SwallowedOutOfMemoryError is returned from checkAllAllocationFailures. You may want to ignore this depending on whether or not the code you're testing includes some strategies for recovering from error.OutOfMemory.
  • If a run of test_fn with an expected allocation failure executes without an allocation failure being induced, then error.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 of test_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);
}

Params

File

testing.zig:1142
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
pub fn checkAllAllocationFailures(
    backing_allocator: std.mem.Allocator,
    comptime test_fn: anytype,
    extra_args: CheckAllAllocationFailuresExtraArgs(@TypeOf(test_fn)),
) !void {
    // 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, .{});

        try @call(.auto, test_fn, .{failing_allocator_inst.allocator()} ++ extra_args);
        break :x failing_allocator_inst.alloc_index;
    };

    for (0..needed_alloc_count) |fail_index| {
        var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{
            .fail_index = fail_index,
        });

        if (@call(.auto, test_fn, .{failing_allocator_inst.allocator()} ++ extra_args)) |_| {
            if (failing_allocator_inst.has_induced_failure) {
                return error.SwallowedOutOfMemoryError;
            } else {
                return error.NondeterministicMemoryUsage;
            }
        } else |err| switch (err) {
            error.OutOfMemory => {
                if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {
                    failPrint(
                        "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}",
                        .{
                            fail_index,
                            needed_alloc_count,
                            failing_allocator_inst.allocated_bytes,
                            failing_allocator_inst.freed_bytes,
                            failing_allocator_inst.allocations,
                            failing_allocator_inst.deallocations,
                            std.debug.FormatStackTrace{
                                .stack_trace = failing_allocator_inst.getStackTrace(),
                            },
                        },
                    );
                    return error.MemoryLeakDetected;
                }
            },
            else => |e| return e,
        }
    }
}