| 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,8 +19,8 @@ const windows = std.os.windows; |
| 19 | 19 | ||
| 20 | pub const leb = @import("debug/leb128.zig"); | 20 | pub const leb = @import("debug/leb128.zig"); |
| 21 | 21 | ||
| 22 | pub const FailingAllocator = @import("debug/failing_allocator.zig").FailingAllocator; | 22 | pub const global_allocator = @compileError("Please switch to std.testing.leak_count_allocator."); |
| 23 | pub const failing_allocator = &FailingAllocator.init(&global_fixed_allocator.allocator, 0).allocator; | 23 | pub const failing_allocator = @compileError("Please switch to std.testing.failing_allocator."); |
| 24 | 24 | ||
| 25 | pub const runtime_safety = switch (builtin.mode) { | 25 | pub const runtime_safety = switch (builtin.mode) { |
| 26 | .Debug, .ReleaseSafe => true, | 26 | .Debug, .ReleaseSafe => true, |
| ... | @@ -2192,12 +2192,6 @@ fn readInitialLength(comptime E: type, in_stream: *io.InStream(E), is_64: *bool) | ... | @@ -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 | /// TODO multithreaded awareness | 2195 | /// TODO multithreaded awareness |
| 2202 | var debug_info_allocator: ?*mem.Allocator = null; | 2196 | var debug_info_allocator: ?*mem.Allocator = null; |
| 2203 | var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; | 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,7 +891,7 @@ pub fn readLineSlice(slice: []u8) ![]u8 { |
| 891 | pub fn readLineSliceFrom(stream: var, slice: []u8) ![]u8 { | 891 | pub fn readLineSliceFrom(stream: var, slice: []u8) ![]u8 { |
| 892 | // We cannot use Buffer.fromOwnedSlice, as it wants to append a null byte | 892 | // We cannot use Buffer.fromOwnedSlice, as it wants to append a null byte |
| 893 | // after taking ownership, which would always require an allocation. | 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 | try buf.resize(0); | 895 | try buf.resize(0); |
| 896 | return try readLineFrom(stream, &buf); | 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,47 +7,12 @@ pub const allocator = &allocator_instance.allocator; |
| 7 | pub var allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]); | 7 | pub var allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]); |
| 8 | var allocator_mem: [100 * 1024]u8 = undefined; | 8 | var allocator_mem: [100 * 1024]u8 = undefined; |
| 9 | 9 | ||
| 10 | pub const leak_count_allocator = &leak_count_allocator_instance.allocator; | 10 | pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator; |
| 11 | pub var leak_count_allocator_instance = LeakCountAllocator.init(allocator); | 11 | pub const failing_allocator = &FailingAllocator.init(allocator, 0).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 | } | ||
| 27 | 12 | ||
| 28 | fn realloc(allo: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | 13 | pub const LeakCountAllocator = @import("testing/leak_count_allocator.zig").LeakCountAllocator; |
| 29 | const self = @fieldParentPtr(LeakCountAllocator, "allocator", allo); | 14 | pub var leak_count_allocator_instance = LeakCountAllocator.init(allocator); |
| 30 | if (old_mem.len == 0) { | 15 | pub const leak_count_allocator = &leak_count_allocator_instance.allocator; |
| 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 | }; | ||
| 51 | 16 | ||
| 52 | /// This function is intended to be used only in tests. It prints diagnostics to stderr | 17 | /// This function is intended to be used only in tests. It prints diagnostics to stderr |
| 53 | /// and then aborts when actual_error_union is not expected_error. | 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,7 +2773,7 @@ fn testTransform(source: []const u8, expected_source: []const u8) !void { |
| 2773 | const needed_alloc_count = x: { | 2773 | const needed_alloc_count = x: { |
| 2774 | // Try it once with unlimited memory, make sure it works | 2774 | // Try it once with unlimited memory, make sure it works |
| 2775 | var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); | 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 | var anything_changed: bool = undefined; | 2777 | var anything_changed: bool = undefined; |
| 2778 | const result_source = try testParse(source, &failing_allocator.allocator, &anything_changed); | 2778 | const result_source = try testParse(source, &failing_allocator.allocator, &anything_changed); |
| 2779 | if (!mem.eql(u8, result_source, expected_source)) { | 2779 | if (!mem.eql(u8, result_source, expected_source)) { |
| ... | @@ -2797,7 +2797,7 @@ fn testTransform(source: []const u8, expected_source: []const u8) !void { | ... | @@ -2797,7 +2797,7 @@ fn testTransform(source: []const u8, expected_source: []const u8) !void { |
| 2797 | var fail_index: usize = 0; | 2797 | var fail_index: usize = 0; |
| 2798 | while (fail_index < needed_alloc_count) : (fail_index += 1) { | 2798 | while (fail_index < needed_alloc_count) : (fail_index += 1) { |
| 2799 | var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); | 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 | var anything_changed: bool = undefined; | 2801 | var anything_changed: bool = undefined; |
| 2802 | if (testParse(source, &failing_allocator.allocator, &anything_changed)) |_| { | 2802 | if (testParse(source, &failing_allocator.allocator, &anything_changed)) |_| { |
| 2803 | return error.NondeterministicMemoryUsage; | 2803 | return error.NondeterministicMemoryUsage; |