| author | |
| committer | |
| log | 0c137934cbd10528c2dced898b6c5485ab528e6d |
| tree | bb55926127481fa0a81b0c858ceeec42b8526c29 |
| parent | ffd30dbe28efce0b971d69df06ab684ceef0f881 |
7 files changed, 137 insertions(+), 132 deletions(-)
lib/std/debug.zig+2-8| ... | ... | @@ -19,8 +19,8 @@ const windows = std.os.windows; |
| 19 | 19 | |
| 20 | 20 | pub const leb = @import("debug/leb128.zig"); |
| 21 | 21 | |
| 22 | pub const FailingAllocator = @import("debug/failing_allocator.zig").FailingAllocator; | |
| 23 | pub const failing_allocator = &FailingAllocator.init(&global_fixed_allocator.allocator, 0).allocator; | |
| 22 | pub const global_allocator = @compileError("Please switch to std.testing.leak_count_allocator."); | |
| 23 | pub const failing_allocator = @compileError("Please switch to std.testing.failing_allocator."); | |
| 24 | 24 | |
| 25 | 25 | pub const runtime_safety = switch (builtin.mode) { |
| 26 | 26 | .Debug, .ReleaseSafe => true, |
| ... | ... | @@ -2192,12 +2192,6 @@ fn readInitialLength(comptime E: type, in_stream: *io.InStream(E), is_64: *bool) |
| 2192 | 2192 | } |
| 2193 | 2193 | } |
| 2194 | 2194 | |
| 2195 | pub const global_allocator = blk: { | |
| 2196 | @compileError("Please switch to std.testing.leak_count_allocator."); | |
| 2197 | }; | |
| 2198 | var global_fixed_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(global_allocator_mem[0..]); | |
| 2199 | var global_allocator_mem: [100 * 1024]u8 = undefined; | |
| 2200 | ||
| 2201 | 2195 | /// TODO multithreaded awareness |
| 2202 | 2196 | var debug_info_allocator: ?*mem.Allocator = null; |
| 2203 | 2197 | var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; |
lib/std/debug/failing_allocator.zig deleted-81| ... | ... | @@ -1,81 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const mem = std.mem; | |
| 3 | ||
| 4 | /// Allocator that fails after N allocations, useful for making sure out of | |
| 5 | /// memory conditions are handled correctly. | |
| 6 | /// | |
| 7 | /// To use this, first initialize it and get an allocator with | |
| 8 | /// | |
| 9 | /// `const failing_allocator = &FailingAllocator.init(<allocator>, | |
| 10 | /// <fail_index>).allocator;` | |
| 11 | /// | |
| 12 | /// Then use `failing_allocator` anywhere you would have used a | |
| 13 | /// different allocator. | |
| 14 | pub const FailingAllocator = struct { | |
| 15 | allocator: mem.Allocator, | |
| 16 | index: usize, | |
| 17 | fail_index: usize, | |
| 18 | internal_allocator: *mem.Allocator, | |
| 19 | allocated_bytes: usize, | |
| 20 | freed_bytes: usize, | |
| 21 | allocations: usize, | |
| 22 | deallocations: usize, | |
| 23 | ||
| 24 | /// `fail_index` is the number of successful allocations you can | |
| 25 | /// expect from this allocator. The next allocation will fail. | |
| 26 | /// For example, if this is called with `fail_index` equal to 2, | |
| 27 | /// the following test will pass: | |
| 28 | /// | |
| 29 | /// var a = try failing_alloc.create(i32); | |
| 30 | /// var b = try failing_alloc.create(i32); | |
| 31 | /// testing.expectError(error.OutOfMemory, failing_alloc.create(i32)); | |
| 32 | pub fn init(allocator: *mem.Allocator, fail_index: usize) FailingAllocator { | |
| 33 | return FailingAllocator{ | |
| 34 | .internal_allocator = allocator, | |
| 35 | .fail_index = fail_index, | |
| 36 | .index = 0, | |
| 37 | .allocated_bytes = 0, | |
| 38 | .freed_bytes = 0, | |
| 39 | .allocations = 0, | |
| 40 | .deallocations = 0, | |
| 41 | .allocator = mem.Allocator{ | |
| 42 | .reallocFn = realloc, | |
| 43 | .shrinkFn = shrink, | |
| 44 | }, | |
| 45 | }; | |
| 46 | } | |
| 47 | ||
| 48 | fn realloc(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | |
| 49 | const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); | |
| 50 | if (self.index == self.fail_index) { | |
| 51 | return error.OutOfMemory; | |
| 52 | } | |
| 53 | const result = try self.internal_allocator.reallocFn( | |
| 54 | self.internal_allocator, | |
| 55 | old_mem, | |
| 56 | old_align, | |
| 57 | new_size, | |
| 58 | new_align, | |
| 59 | ); | |
| 60 | if (new_size < old_mem.len) { | |
| 61 | self.freed_bytes += old_mem.len - new_size; | |
| 62 | if (new_size == 0) | |
| 63 | self.deallocations += 1; | |
| 64 | } else if (new_size > old_mem.len) { | |
| 65 | self.allocated_bytes += new_size - old_mem.len; | |
| 66 | if (old_mem.len == 0) | |
| 67 | self.allocations += 1; | |
| 68 | } | |
| 69 | self.index += 1; | |
| 70 | return result; | |
| 71 | } | |
| 72 | ||
| 73 | fn shrink(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | |
| 74 | const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); | |
| 75 | const r = self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align); | |
| 76 | self.freed_bytes += old_mem.len - r.len; | |
| 77 | if (new_size == 0) | |
| 78 | self.deallocations += 1; | |
| 79 | return r; | |
| 80 | } | |
| 81 | }; |
lib/std/io.zig+1-1| ... | ... | @@ -891,7 +891,7 @@ pub fn readLineSlice(slice: []u8) ![]u8 { |
| 891 | 891 | pub fn readLineSliceFrom(stream: var, slice: []u8) ![]u8 { |
| 892 | 892 | // We cannot use Buffer.fromOwnedSlice, as it wants to append a null byte |
| 893 | 893 | // after taking ownership, which would always require an allocation. |
| 894 | var buf = std.Buffer{ .list = std.ArrayList(u8).fromOwnedSlice(debug.failing_allocator, slice) }; | |
| 894 | var buf = std.Buffer{ .list = std.ArrayList(u8).fromOwnedSlice(testing.failing_allocator, slice) }; | |
| 895 | 895 | try buf.resize(0); |
| 896 | 896 | return try readLineFrom(stream, &buf); |
| 897 | 897 | } |
lib/std/testing.zig+5-40| ... | ... | @@ -7,47 +7,12 @@ pub const allocator = &allocator_instance.allocator; |
| 7 | 7 | pub var allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]); |
| 8 | 8 | var allocator_mem: [100 * 1024]u8 = undefined; |
| 9 | 9 | |
| 10 | pub const leak_count_allocator = &leak_count_allocator_instance.allocator; | |
| 11 | pub var leak_count_allocator_instance = LeakCountAllocator.init(allocator); | |
| 12 | const LeakCountAllocator = struct { | |
| 13 | count: usize, | |
| 14 | allocator: std.mem.Allocator, | |
| 15 | internal_allocator: *std.mem.Allocator, | |
| 16 | ||
| 17 | fn init(allo: *std.mem.Allocator) LeakCountAllocator { | |
| 18 | return .{ | |
| 19 | .count = 0, | |
| 20 | .allocator = .{ | |
| 21 | .reallocFn = realloc, | |
| 22 | .shrinkFn = shrink, | |
| 23 | }, | |
| 24 | .internal_allocator = allo, | |
| 25 | }; | |
| 26 | } | |
| 10 | pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator; | |
| 11 | pub const failing_allocator = &FailingAllocator.init(allocator, 0).allocator; | |
| 27 | 12 | |
| 28 | fn realloc(allo: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | |
| 29 | const self = @fieldParentPtr(LeakCountAllocator, "allocator", allo); | |
| 30 | if (old_mem.len == 0) { | |
| 31 | self.count += 1; | |
| 32 | } | |
| 33 | return self.internal_allocator.reallocFn(self.internal_allocator, old_mem, old_align, new_size, new_align); | |
| 34 | } | |
| 35 | ||
| 36 | fn shrink(allo: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | |
| 37 | const self = @fieldParentPtr(LeakCountAllocator, "allocator", allo); | |
| 38 | if (new_size == 0) { | |
| 39 | self.count -= 1; | |
| 40 | } | |
| 41 | return self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align); | |
| 42 | } | |
| 43 | ||
| 44 | fn validate(self: LeakCountAllocator) !void { | |
| 45 | if (self.count > 0) { | |
| 46 | std.debug.warn("Detected leaked allocations without matching free: {}\n", .{self.count}); | |
| 47 | return error.Leak; | |
| 48 | } | |
| 49 | } | |
| 50 | }; | |
| 13 | pub const LeakCountAllocator = @import("testing/leak_count_allocator.zig").LeakCountAllocator; | |
| 14 | pub var leak_count_allocator_instance = LeakCountAllocator.init(allocator); | |
| 15 | pub const leak_count_allocator = &leak_count_allocator_instance.allocator; | |
| 51 | 16 | |
| 52 | 17 | /// This function is intended to be used only in tests. It prints diagnostics to stderr |
| 53 | 18 | /// and then aborts when actual_error_union is not expected_error. |
lib/std/testing/failing_allocator.zig created+81| ... | ... | @@ -0,0 +1,81 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const mem = std.mem; | |
| 3 | ||
| 4 | /// Allocator that fails after N allocations, useful for making sure out of | |
| 5 | /// memory conditions are handled correctly. | |
| 6 | /// | |
| 7 | /// To use this, first initialize it and get an allocator with | |
| 8 | /// | |
| 9 | /// `const failing_allocator = &FailingAllocator.init(<allocator>, | |
| 10 | /// <fail_index>).allocator;` | |
| 11 | /// | |
| 12 | /// Then use `failing_allocator` anywhere you would have used a | |
| 13 | /// different allocator. | |
| 14 | pub const FailingAllocator = struct { | |
| 15 | allocator: mem.Allocator, | |
| 16 | index: usize, | |
| 17 | fail_index: usize, | |
| 18 | internal_allocator: *mem.Allocator, | |
| 19 | allocated_bytes: usize, | |
| 20 | freed_bytes: usize, | |
| 21 | allocations: usize, | |
| 22 | deallocations: usize, | |
| 23 | ||
| 24 | /// `fail_index` is the number of successful allocations you can | |
| 25 | /// expect from this allocator. The next allocation will fail. | |
| 26 | /// For example, if this is called with `fail_index` equal to 2, | |
| 27 | /// the following test will pass: | |
| 28 | /// | |
| 29 | /// var a = try failing_alloc.create(i32); | |
| 30 | /// var b = try failing_alloc.create(i32); | |
| 31 | /// testing.expectError(error.OutOfMemory, failing_alloc.create(i32)); | |
| 32 | pub fn init(allocator: *mem.Allocator, fail_index: usize) FailingAllocator { | |
| 33 | return FailingAllocator{ | |
| 34 | .internal_allocator = allocator, | |
| 35 | .fail_index = fail_index, | |
| 36 | .index = 0, | |
| 37 | .allocated_bytes = 0, | |
| 38 | .freed_bytes = 0, | |
| 39 | .allocations = 0, | |
| 40 | .deallocations = 0, | |
| 41 | .allocator = mem.Allocator{ | |
| 42 | .reallocFn = realloc, | |
| 43 | .shrinkFn = shrink, | |
| 44 | }, | |
| 45 | }; | |
| 46 | } | |
| 47 | ||
| 48 | fn realloc(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | |
| 49 | const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); | |
| 50 | if (self.index == self.fail_index) { | |
| 51 | return error.OutOfMemory; | |
| 52 | } | |
| 53 | const result = try self.internal_allocator.reallocFn( | |
| 54 | self.internal_allocator, | |
| 55 | old_mem, | |
| 56 | old_align, | |
| 57 | new_size, | |
| 58 | new_align, | |
| 59 | ); | |
| 60 | if (new_size < old_mem.len) { | |
| 61 | self.freed_bytes += old_mem.len - new_size; | |
| 62 | if (new_size == 0) | |
| 63 | self.deallocations += 1; | |
| 64 | } else if (new_size > old_mem.len) { | |
| 65 | self.allocated_bytes += new_size - old_mem.len; | |
| 66 | if (old_mem.len == 0) | |
| 67 | self.allocations += 1; | |
| 68 | } | |
| 69 | self.index += 1; | |
| 70 | return result; | |
| 71 | } | |
| 72 | ||
| 73 | fn shrink(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | |
| 74 | const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); | |
| 75 | const r = self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align); | |
| 76 | self.freed_bytes += old_mem.len - r.len; | |
| 77 | if (new_size == 0) | |
| 78 | self.deallocations += 1; | |
| 79 | return r; | |
| 80 | } | |
| 81 | }; |
lib/std/testing/leak_count_allocator.zig created+46| ... | ... | @@ -0,0 +1,46 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | ||
| 3 | /// This allocator is used in front of another allocator and counts the numbers of allocs and frees. | |
| 4 | /// The test runner asserts every alloc has a corresponding free at the end of each test. | |
| 5 | /// | |
| 6 | /// The detection algorithm is incredibly primitive and only accounts for number of calls. | |
| 7 | /// This should be replaced by the general purpose debug allocator. | |
| 8 | pub const LeakCountAllocator = struct { | |
| 9 | count: usize, | |
| 10 | allocator: std.mem.Allocator, | |
| 11 | internal_allocator: *std.mem.Allocator, | |
| 12 | ||
| 13 | pub fn init(allocator: *std.mem.Allocator) LeakCountAllocator { | |
| 14 | return .{ | |
| 15 | .count = 0, | |
| 16 | .allocator = .{ | |
| 17 | .reallocFn = realloc, | |
| 18 | .shrinkFn = shrink, | |
| 19 | }, | |
| 20 | .internal_allocator = allocator, | |
| 21 | }; | |
| 22 | } | |
| 23 | ||
| 24 | fn realloc(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | |
| 25 | const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator); | |
| 26 | if (old_mem.len == 0) { | |
| 27 | self.count += 1; | |
| 28 | } | |
| 29 | return self.internal_allocator.reallocFn(self.internal_allocator, old_mem, old_align, new_size, new_align); | |
| 30 | } | |
| 31 | ||
| 32 | fn shrink(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | |
| 33 | const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator); | |
| 34 | if (new_size == 0) { | |
| 35 | self.count -= 1; | |
| 36 | } | |
| 37 | return self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align); | |
| 38 | } | |
| 39 | ||
| 40 | pub fn validate(self: LeakCountAllocator) !void { | |
| 41 | if (self.count > 0) { | |
| 42 | std.debug.warn("Detected leaked allocations without matching free: {}\n", .{self.count}); | |
| 43 | return error.Leak; | |
| 44 | } | |
| 45 | } | |
| 46 | }; |
lib/std/zig/parser_test.zig+2-2| ... | ... | @@ -2773,7 +2773,7 @@ fn testTransform(source: []const u8, expected_source: []const u8) !void { |
| 2773 | 2773 | const needed_alloc_count = x: { |
| 2774 | 2774 | // Try it once with unlimited memory, make sure it works |
| 2775 | 2775 | var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); |
| 2776 | var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, maxInt(usize)); | |
| 2776 | var failing_allocator = std.testing.FailingAllocator.init(&fixed_allocator.allocator, maxInt(usize)); | |
| 2777 | 2777 | var anything_changed: bool = undefined; |
| 2778 | 2778 | const result_source = try testParse(source, &failing_allocator.allocator, &anything_changed); |
| 2779 | 2779 | if (!mem.eql(u8, result_source, expected_source)) { |
| ... | ... | @@ -2797,7 +2797,7 @@ fn testTransform(source: []const u8, expected_source: []const u8) !void { |
| 2797 | 2797 | var fail_index: usize = 0; |
| 2798 | 2798 | while (fail_index < needed_alloc_count) : (fail_index += 1) { |
| 2799 | 2799 | var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); |
| 2800 | var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, fail_index); | |
| 2800 | var failing_allocator = std.testing.FailingAllocator.init(&fixed_allocator.allocator, fail_index); | |
| 2801 | 2801 | var anything_changed: bool = undefined; |
| 2802 | 2802 | if (testParse(source, &failing_allocator.allocator, &anything_changed)) |_| { |
| 2803 | 2803 | return error.NondeterministicMemoryUsage; |