authorgravatar for benjamin.feng@glassdoor.comBenjamin Feng <benjamin.feng@glassdoor.com> 2020-01-29 17:26:10-06:00
committergravatar for benjamin.feng@glassdoor.comBenjamin Feng <benjamin.feng@glassdoor.com> 2020-01-29 17:38:42-06:00
log0c137934cbd10528c2dced898b6c5485ab528e6d
treebb55926127481fa0a81b0c858ceeec42b8526c29
parentffd30dbe28efce0b971d69df06ab684ceef0f881

Move FailingAllocator to testing


7 files changed, 137 insertions(+), 132 deletions(-)

lib/std/debug.zig+2-8
......@@ -19,8 +19,8 @@ const windows = std.os.windows;
1919
2020pub const leb = @import("debug/leb128.zig");
2121
22pub const FailingAllocator = @import("debug/failing_allocator.zig").FailingAllocator;
23pub const failing_allocator = &FailingAllocator.init(&global_fixed_allocator.allocator, 0).allocator;
22pub const global_allocator = @compileError("Please switch to std.testing.leak_count_allocator.");
23pub const failing_allocator = @compileError("Please switch to std.testing.failing_allocator.");
2424
2525pub const runtime_safety = switch (builtin.mode) {
2626 .Debug, .ReleaseSafe => true,
......@@ -2192,12 +2192,6 @@ fn readInitialLength(comptime E: type, in_stream: *io.InStream(E), is_64: *bool)
21922192 }
21932193}
21942194
2195pub const global_allocator = blk: {
2196 @compileError("Please switch to std.testing.leak_count_allocator.");
2197};
2198var global_fixed_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(global_allocator_mem[0..]);
2199var global_allocator_mem: [100 * 1024]u8 = undefined;
2200
22012195/// TODO multithreaded awareness
22022196var debug_info_allocator: ?*mem.Allocator = null;
22032197var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
lib/std/debug/failing_allocator.zig deleted-81
......@@ -1,81 +0,0 @@
1const std = @import("../std.zig");
2const 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.
14pub 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 {
891891pub fn readLineSliceFrom(stream: var, slice: []u8) ![]u8 {
892892 // We cannot use Buffer.fromOwnedSlice, as it wants to append a null byte
893893 // 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) };
895895 try buf.resize(0);
896896 return try readLineFrom(stream, &buf);
897897}
lib/std/testing.zig+5-40
......@@ -7,47 +7,12 @@ pub const allocator = &allocator_instance.allocator;
77pub var allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]);
88var allocator_mem: [100 * 1024]u8 = undefined;
99
10pub const leak_count_allocator = &leak_count_allocator_instance.allocator;
11pub var leak_count_allocator_instance = LeakCountAllocator.init(allocator);
12const 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 }
10pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator;
11pub const failing_allocator = &FailingAllocator.init(allocator, 0).allocator;
2712
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};
13pub const LeakCountAllocator = @import("testing/leak_count_allocator.zig").LeakCountAllocator;
14pub var leak_count_allocator_instance = LeakCountAllocator.init(allocator);
15pub const leak_count_allocator = &leak_count_allocator_instance.allocator;
5116
5217/// This function is intended to be used only in tests. It prints diagnostics to stderr
5318/// and then aborts when actual_error_union is not expected_error.
lib/std/testing/failing_allocator.zig created+81
......@@ -0,0 +1,81 @@
1const std = @import("../std.zig");
2const 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.
14pub 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 @@
1const 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.
8pub 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 {
27732773 const needed_alloc_count = x: {
27742774 // Try it once with unlimited memory, make sure it works
27752775 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));
27772777 var anything_changed: bool = undefined;
27782778 const result_source = try testParse(source, &failing_allocator.allocator, &anything_changed);
27792779 if (!mem.eql(u8, result_source, expected_source)) {
......@@ -2797,7 +2797,7 @@ fn testTransform(source: []const u8, expected_source: []const u8) !void {
27972797 var fail_index: usize = 0;
27982798 while (fail_index < needed_alloc_count) : (fail_index += 1) {
27992799 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);
28012801 var anything_changed: bool = undefined;
28022802 if (testParse(source, &failing_allocator.allocator, &anything_changed)) |_| {
28032803 return error.NondeterministicMemoryUsage;