| author | |
| committer | |
| log | a0b2a18648ec4c465f3e19c05338b554c0742dfb |
| tree | 9dff7a6a3cc5ac8d04ec0736caeb284c80523290 |
| parent | 7eeef5fb2b9dc78679f4091e2a8173d07968b3e5 |
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. |
| 8 | pub var random_seed: u32 = 0; | 8 | pub var random_seed: u32 = 0; |
| 9 | 9 | ||
| 10 | pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator; | 10 | pub const FailingAllocator = @import("testing/FailingAllocator.zig"); |
| 11 | pub const failing_allocator = failing_allocator_instance.allocator(); | ||
| 12 | pub var failing_allocator_instance = FailingAllocator.init(base_allocator_instance.allocator(), .{ .fail_index = 0 }); | ||
| 11 | 13 | ||
| 12 | /// This should only be used in temporary test programs. | 14 | /// This should only be used in temporary test programs. |
| 13 | pub const allocator = allocator_instance.allocator(); | 15 | pub 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 | }; |
| 19 | 21 | ||
| 20 | pub const failing_allocator = failing_allocator_instance.allocator(); | ||
| 21 | pub var failing_allocator_instance = FailingAllocator.init(base_allocator_instance.allocator(), .{ .fail_index = 0 }); | ||
| 22 | |||
| 23 | pub var base_allocator_instance = std.heap.FixedBufferAllocator.init(""); | 22 | pub var base_allocator_instance = std.heap.FixedBufferAllocator.init(""); |
| 24 | 23 | ||
| 25 | /// TODO https://github.com/ziglang/zig/issues/5738 | 24 | /// 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. | ||
| 11 | const std = @import("../std.zig"); | ||
| 12 | const mem = std.mem; | ||
| 13 | const FailingAllocator = @This(); | ||
| 14 | |||
| 15 | alloc_index: usize, | ||
| 16 | resize_index: usize, | ||
| 17 | internal_allocator: mem.Allocator, | ||
| 18 | allocated_bytes: usize, | ||
| 19 | freed_bytes: usize, | ||
| 20 | allocations: usize, | ||
| 21 | deallocations: usize, | ||
| 22 | stack_addresses: [num_stack_frames]usize, | ||
| 23 | has_induced_failure: bool, | ||
| 24 | fail_index: usize, | ||
| 25 | resize_fail_index: usize, | ||
| 26 | |||
| 27 | const num_stack_frames = if (std.debug.sys_can_stack_trace) 16 else 0; | ||
| 28 | |||
| 29 | pub 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 | |||
| 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 | } | ||
lib/std/testing/failing_allocator.zig deleted-162| ... | @@ -1,162 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const mem = std.mem; | ||
| 3 | |||
| 4 | pub 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. | ||
| 28 | pub 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 | }; | ||