authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-03 20:00:01-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-06 14:23:23-08:00
loga0b2a18648ec4c465f3e19c05338b554c0742dfb
tree9dff7a6a3cc5ac8d04ec0736caeb284c80523290
parent7eeef5fb2b9dc78679f4091e2a8173d07968b3e5

std.testing.FailingAllocator: flatten namespace


3 files changed, 164 insertions(+), 166 deletions(-)

lib/std/testing.zig+3-4
...@@ -7,7 +7,9 @@ const math = std.math;...@@ -7,7 +7,9 @@ const math = std.math;
7/// Initialized on startup. Read-only after that.7/// Initialized on startup. Read-only after that.
8pub var random_seed: u32 = 0;8pub var random_seed: u32 = 0;
99
10pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator;10pub const FailingAllocator = @import("testing/FailingAllocator.zig");
11pub const failing_allocator = failing_allocator_instance.allocator();
12pub var failing_allocator_instance = FailingAllocator.init(base_allocator_instance.allocator(), .{ .fail_index = 0 });
1113
12/// This should only be used in temporary test programs.14/// This should only be used in temporary test programs.
13pub const allocator = allocator_instance.allocator();15pub const allocator = allocator_instance.allocator();
...@@ -17,9 +19,6 @@ pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{}) = b: {...@@ -17,9 +19,6 @@ pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{}) = b: {
17 break :b .init;19 break :b .init;
18};20};
1921
20pub const failing_allocator = failing_allocator_instance.allocator();
21pub var failing_allocator_instance = FailingAllocator.init(base_allocator_instance.allocator(), .{ .fail_index = 0 });
22
23pub var base_allocator_instance = std.heap.FixedBufferAllocator.init("");22pub var base_allocator_instance = std.heap.FixedBufferAllocator.init("");
2423
25/// TODO https://github.com/ziglang/zig/issues/573824/// TODO https://github.com/ziglang/zig/issues/5738
lib/std/testing/FailingAllocator.zig created+161
...@@ -0,0 +1,161 @@
1//! Allocator that fails after N allocations, useful for making sure out of
2//! memory conditions are handled correctly.
3//!
4//! To use this, first initialize it and get an allocator with
5//!
6//! `const failing_allocator = &FailingAllocator.init(<allocator>,
7//! <config>).allocator;`
8//!
9//! Then use `failing_allocator` anywhere you would have used a
10//! different allocator.
11const std = @import("../std.zig");
12const mem = std.mem;
13const FailingAllocator = @This();
14
15alloc_index: usize,
16resize_index: usize,
17internal_allocator: mem.Allocator,
18allocated_bytes: usize,
19freed_bytes: usize,
20allocations: usize,
21deallocations: usize,
22stack_addresses: [num_stack_frames]usize,
23has_induced_failure: bool,
24fail_index: usize,
25resize_fail_index: usize,
26
27const num_stack_frames = if (std.debug.sys_can_stack_trace) 16 else 0;
28
29pub const Config = struct {
30 /// The number of successful allocations you can expect from this allocator.
31 /// The next allocation will fail. For example, with `fail_index` equal to
32 /// 2, the following test will pass:
33 ///
34 /// var a = try failing_alloc.create(i32);
35 /// var b = try failing_alloc.create(i32);
36 /// testing.expectError(error.OutOfMemory, failing_alloc.create(i32));
37 fail_index: usize = std.math.maxInt(usize),
38
39 /// Number of successful resizes to expect from this allocator. The next resize will fail.
40 resize_fail_index: usize = std.math.maxInt(usize),
41};
42
43pub fn init(internal_allocator: mem.Allocator, config: Config) FailingAllocator {
44 return FailingAllocator{
45 .internal_allocator = internal_allocator,
46 .alloc_index = 0,
47 .resize_index = 0,
48 .allocated_bytes = 0,
49 .freed_bytes = 0,
50 .allocations = 0,
51 .deallocations = 0,
52 .stack_addresses = undefined,
53 .has_induced_failure = false,
54 .fail_index = config.fail_index,
55 .resize_fail_index = config.resize_fail_index,
56 };
57}
58
59pub fn allocator(self: *FailingAllocator) mem.Allocator {
60 return .{
61 .ptr = self,
62 .vtable = &.{
63 .alloc = alloc,
64 .resize = resize,
65 .remap = remap,
66 .free = free,
67 },
68 };
69}
70
71fn alloc(
72 ctx: *anyopaque,
73 len: usize,
74 alignment: mem.Alignment,
75 return_address: usize,
76) ?[*]u8 {
77 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
78 if (self.alloc_index == self.fail_index) {
79 if (!self.has_induced_failure) {
80 @memset(&self.stack_addresses, 0);
81 var stack_trace = std.builtin.StackTrace{
82 .instruction_addresses = &self.stack_addresses,
83 .index = 0,
84 };
85 std.debug.captureStackTrace(return_address, &stack_trace);
86 self.has_induced_failure = true;
87 }
88 return null;
89 }
90 const result = self.internal_allocator.rawAlloc(len, alignment, return_address) orelse
91 return null;
92 self.allocated_bytes += len;
93 self.allocations += 1;
94 self.alloc_index += 1;
95 return result;
96}
97
98fn resize(
99 ctx: *anyopaque,
100 memory: []u8,
101 alignment: mem.Alignment,
102 new_len: usize,
103 ra: usize,
104) bool {
105 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
106 if (self.resize_index == self.resize_fail_index)
107 return false;
108 if (!self.internal_allocator.rawResize(memory, alignment, new_len, ra))
109 return false;
110 if (new_len < memory.len) {
111 self.freed_bytes += memory.len - new_len;
112 } else {
113 self.allocated_bytes += new_len - memory.len;
114 }
115 self.resize_index += 1;
116 return true;
117}
118
119fn remap(
120 ctx: *anyopaque,
121 memory: []u8,
122 alignment: mem.Alignment,
123 new_len: usize,
124 ra: usize,
125) ?[*]u8 {
126 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
127 if (self.resize_index == self.resize_fail_index) return null;
128 const new_ptr = self.internal_allocator.rawRemap(memory, alignment, new_len, ra) orelse return null;
129 if (new_len < memory.len) {
130 self.freed_bytes += memory.len - new_len;
131 } else {
132 self.allocated_bytes += new_len - memory.len;
133 }
134 self.resize_index += 1;
135 return new_ptr;
136}
137
138fn free(
139 ctx: *anyopaque,
140 old_mem: []u8,
141 alignment: mem.Alignment,
142 ra: usize,
143) void {
144 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
145 self.internal_allocator.rawFree(old_mem, alignment, ra);
146 self.deallocations += 1;
147 self.freed_bytes += old_mem.len;
148}
149
150/// Only valid once `has_induced_failure == true`
151pub fn getStackTrace(self: *FailingAllocator) std.builtin.StackTrace {
152 std.debug.assert(self.has_induced_failure);
153 var len: usize = 0;
154 while (len < self.stack_addresses.len and self.stack_addresses[len] != 0) {
155 len += 1;
156 }
157 return .{
158 .instruction_addresses = &self.stack_addresses,
159 .index = len,
160 };
161}
lib/std/testing/failing_allocator.zig deleted-162
...@@ -1,162 +0,0 @@
1const std = @import("../std.zig");
2const mem = std.mem;
3
4pub const Config = struct {
5 /// The number of successful allocations you can expect from this allocator.
6 /// The next allocation will fail. For example, with `fail_index` equal to
7 /// 2, the following test will pass:
8 ///
9 /// var a = try failing_alloc.create(i32);
10 /// var b = try failing_alloc.create(i32);
11 /// testing.expectError(error.OutOfMemory, failing_alloc.create(i32));
12 fail_index: usize = std.math.maxInt(usize),
13
14 /// Number of successful resizes to expect from this allocator. The next resize will fail.
15 resize_fail_index: usize = std.math.maxInt(usize),
16};
17
18/// Allocator that fails after N allocations, useful for making sure out of
19/// memory conditions are handled correctly.
20///
21/// To use this, first initialize it and get an allocator with
22///
23/// `const failing_allocator = &FailingAllocator.init(<allocator>,
24/// <config>).allocator;`
25///
26/// Then use `failing_allocator` anywhere you would have used a
27/// different allocator.
28pub const FailingAllocator = struct {
29 alloc_index: usize,
30 resize_index: usize,
31 internal_allocator: mem.Allocator,
32 allocated_bytes: usize,
33 freed_bytes: usize,
34 allocations: usize,
35 deallocations: usize,
36 stack_addresses: [num_stack_frames]usize,
37 has_induced_failure: bool,
38 fail_index: usize,
39 resize_fail_index: usize,
40
41 const num_stack_frames = if (std.debug.sys_can_stack_trace) 16 else 0;
42
43 pub fn init(internal_allocator: mem.Allocator, config: Config) FailingAllocator {
44 return FailingAllocator{
45 .internal_allocator = internal_allocator,
46 .alloc_index = 0,
47 .resize_index = 0,
48 .allocated_bytes = 0,
49 .freed_bytes = 0,
50 .allocations = 0,
51 .deallocations = 0,
52 .stack_addresses = undefined,
53 .has_induced_failure = false,
54 .fail_index = config.fail_index,
55 .resize_fail_index = config.resize_fail_index,
56 };
57 }
58
59 pub fn allocator(self: *FailingAllocator) mem.Allocator {
60 return .{
61 .ptr = self,
62 .vtable = &.{
63 .alloc = alloc,
64 .resize = resize,
65 .remap = remap,
66 .free = free,
67 },
68 };
69 }
70
71 fn alloc(
72 ctx: *anyopaque,
73 len: usize,
74 alignment: mem.Alignment,
75 return_address: usize,
76 ) ?[*]u8 {
77 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
78 if (self.alloc_index == self.fail_index) {
79 if (!self.has_induced_failure) {
80 @memset(&self.stack_addresses, 0);
81 var stack_trace = std.builtin.StackTrace{
82 .instruction_addresses = &self.stack_addresses,
83 .index = 0,
84 };
85 std.debug.captureStackTrace(return_address, &stack_trace);
86 self.has_induced_failure = true;
87 }
88 return null;
89 }
90 const result = self.internal_allocator.rawAlloc(len, alignment, return_address) orelse
91 return null;
92 self.allocated_bytes += len;
93 self.allocations += 1;
94 self.alloc_index += 1;
95 return result;
96 }
97
98 fn resize(
99 ctx: *anyopaque,
100 memory: []u8,
101 alignment: mem.Alignment,
102 new_len: usize,
103 ra: usize,
104 ) bool {
105 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
106 if (self.resize_index == self.resize_fail_index)
107 return false;
108 if (!self.internal_allocator.rawResize(memory, alignment, new_len, ra))
109 return false;
110 if (new_len < memory.len) {
111 self.freed_bytes += memory.len - new_len;
112 } else {
113 self.allocated_bytes += new_len - memory.len;
114 }
115 self.resize_index += 1;
116 return true;
117 }
118
119 fn remap(
120 ctx: *anyopaque,
121 memory: []u8,
122 alignment: mem.Alignment,
123 new_len: usize,
124 ra: usize,
125 ) ?[*]u8 {
126 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
127 if (self.resize_index == self.resize_fail_index) return null;
128 const new_ptr = self.internal_allocator.rawRemap(memory, alignment, new_len, ra) orelse return null;
129 if (new_len < memory.len) {
130 self.freed_bytes += memory.len - new_len;
131 } else {
132 self.allocated_bytes += new_len - memory.len;
133 }
134 self.resize_index += 1;
135 return new_ptr;
136 }
137
138 fn free(
139 ctx: *anyopaque,
140 old_mem: []u8,
141 alignment: mem.Alignment,
142 ra: usize,
143 ) void {
144 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
145 self.internal_allocator.rawFree(old_mem, alignment, ra);
146 self.deallocations += 1;
147 self.freed_bytes += old_mem.len;
148 }
149
150 /// Only valid once `has_induced_failure == true`
151 pub fn getStackTrace(self: *FailingAllocator) std.builtin.StackTrace {
152 std.debug.assert(self.has_induced_failure);
153 var len: usize = 0;
154 while (len < self.stack_addresses.len and self.stack_addresses[len] != 0) {
155 len += 1;
156 }
157 return .{
158 .instruction_addresses = &self.stack_addresses,
159 .index = len,
160 };
161 }
162};