lib/alloc/src/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const alloc = @import("root.zig");
3 const alloc_phase = @import("alloc_phase");
4 const diagnostics = @import("diagnostics.zig");
5 const policy = @import("policy.zig");
6
7 const test_allocator_mode = alloc.test_allocator_mode;
8 const allocator_diagnostics_mode = alloc.allocator_diagnostics_mode;
9 const process_phase_mode = alloc.process_phase_mode;
10 const longLivedProcessAllocator = alloc.longLivedProcessAllocator;
11 const interactiveSessionAllocator = alloc.interactiveSessionAllocator;
12 const standaloneProcessAllocator = alloc.standaloneProcessAllocator;
13 const workerAllocator = alloc.workerAllocator;
14 const threadedIoAllocator = alloc.threadedIoAllocator;
15 const temporaryScratchAllocator = alloc.temporaryScratchAllocator;
16 const testIntegrationAllocator = alloc.testIntegrationAllocator;
17 const diagnosticAllocatorConfig = alloc.diagnosticAllocatorConfig;
18 const diagnosticAllocatorReport = alloc.diagnosticAllocatorReport;
19 const sealProcessAllocatorPhase = alloc.sealProcessAllocatorPhase;
20 const beginProcessAllocatorTeardown = alloc.beginProcessAllocatorTeardown;
21 const processAllocatorPhase = alloc.processAllocatorPhase;
22 const processAllocatorPhaseViolations = alloc.processAllocatorPhaseViolations;
23 const processAllocatorPhaseViolationSites = alloc.processAllocatorPhaseViolationSites;
24 const ProcessAllocatorOwner = alloc.ProcessAllocatorOwner;
25 const ProcessAllocatorFinish = alloc.ProcessAllocatorFinish;
26
27 test "alloc package namespace" {
28 std.testing.refAllDecls(alloc);
29 }
30
31 test "page namespace" {
32 std.testing.refAllDecls(@import("page/root.zig"));
33 }
34
35 test "public phase allocator interoperates with internal phase consumers" {
36 var phase: alloc_phase.SealedPhaseAllocator =
37 try alloc.phase.SealedPhaseAllocator.init(std.testing.allocator);
38 const expected = [_]u8{ 3, 1, 4, 1 };
39 const storage = phase.initializationAllocator().alloc(u8, expected.len) catch |err| {
40 phase.abortInitialization();
41 phase.deinit();
42 return err;
43 };
44 @memcpy(storage, &expected);
45
46 phase.seal();
47 const state: alloc.phase.capacity.Phase = phase.phase();
48 const violations: alloc.phase.PhaseViolations = phase.violations();
49 phase.beginTeardown();
50 defer phase.deinit();
51 defer phase.teardownAllocator().free(storage);
52
53 try std.testing.expectEqual(.steady, state);
54 try std.testing.expectEqual(@as(u64, 0), violations.total());
55 try std.testing.expectEqualSlices(u8, &expected, storage);
56 }
57
58 fn expectSameAllocator(expected: std.mem.Allocator, actual: std.mem.Allocator) !void {
59 try std.testing.expectEqual(expected.ptr, actual.ptr);
60 try std.testing.expectEqual(expected.vtable, actual.vtable);
61 }
62
63 test "allocator policy separates fast integration tests from leak-checking tests" {
64 const diagnostic_allocator = std.testing.allocator;
65 const policy_allocator = policy.platformProcessAllocator();
66 const expected_test_allocator = switch (test_allocator_mode) {
67 .fast => policy_allocator,
68 .leak_check => diagnostic_allocator,
69 };
70
71 try expectSameAllocator(policy_allocator, longLivedProcessAllocator());
72 try expectSameAllocator(expected_test_allocator, interactiveSessionAllocator(diagnostic_allocator));
73 try expectSameAllocator(expected_test_allocator, standaloneProcessAllocator());
74 try expectSameAllocator(expected_test_allocator, testIntegrationAllocator());
75 try expectSameAllocator(expected_test_allocator, workerAllocator());
76 try expectSameAllocator(expected_test_allocator, threadedIoAllocator());
77 try expectSameAllocator(expected_test_allocator, temporaryScratchAllocator());
78 }
79
80 test "allocator diagnostics mode is exposed as deadalloc config" {
81 const config = diagnosticAllocatorConfig() orelse {
82 try std.testing.expect(allocator_diagnostics_mode == null);
83 return;
84 };
85 try std.testing.expect(config.diagnostics.enabled);
86 try std.testing.expectEqual(allocator_diagnostics_mode.?, config.mode);
87 }
88
89 test "allocator diagnostics mode routes process allocations through deadalloc" {
90 const config = diagnosticAllocatorConfig() orelse {
91 try std.testing.expect(diagnosticAllocatorReport() == null);
92 return;
93 };
94
95 const allocator = longLivedProcessAllocator();
96 try std.testing.expect(allocator.ptr != diagnostics.baseProcessAllocator().ptr or allocator.vtable != diagnostics.baseProcessAllocator().vtable);
97
98 const before = diagnosticAllocatorReport().?;
99 const leaked = try allocator.alloc(u8, 32);
100 @memset(leaked, 0x5a);
101 const during = diagnosticAllocatorReport().?;
102 try std.testing.expect(during.live_allocations >= before.live_allocations + 1);
103 try std.testing.expect(during.live_bytes >= before.live_bytes + 32);
104 allocator.free(leaked);
105
106 const after = diagnosticAllocatorReport().?;
107 try std.testing.expect(after.live_allocations <= during.live_allocations);
108 try std.testing.expectEqual(config.mode, allocator_diagnostics_mode.?);
109 }
110
111 test "process allocator owner follows runtime diagnostics policy" {
112 var process_owner = ProcessAllocatorOwner.init();
113 const allocator = process_owner.allocator();
114 const runtime_allocator = standaloneProcessAllocator();
115
116 if (diagnosticAllocatorConfig() != null or process_phase_mode != .off) {
117 try expectSameAllocator(runtime_allocator, allocator);
118 } else {
119 try std.testing.expect(allocator.ptr != runtime_allocator.ptr or allocator.vtable != runtime_allocator.vtable);
120 try std.testing.expectEqual(ProcessAllocatorFinish.clean, process_owner.finish());
121 }
122 }
123
124 test "process allocator owner returns deadalloc diagnostics" {
125 const config = diagnosticAllocatorConfig() orelse return;
126 var process_owner = ProcessAllocatorOwner.init();
127 const allocator = process_owner.allocator();
128
129 const before = diagnosticAllocatorReport().?;
130 const leaked = try allocator.alloc(u8, 19);
131 defer allocator.free(leaked);
132 @memset(leaked, 0x71);
133
134 const finish = process_owner.finish();
135 const report = finish.diagnosticReport() orelse return error.ExpectedAllocatorDiagnostics;
136 try std.testing.expectEqual(@as(u8, 1), finish.exitCode());
137 try std.testing.expect(report.live_allocations >= before.live_allocations + 1);
138 try std.testing.expect(report.live_bytes >= before.live_bytes + 19);
139 try std.testing.expectEqual(config.mode, allocator_diagnostics_mode.?);
140 }
141
142 test "process allocator owner returns invalid free diagnostics" {
143 const config = diagnosticAllocatorConfig() orelse return;
144 var process_owner = ProcessAllocatorOwner.init();
145 const allocator = process_owner.allocator();
146
147 const before = diagnosticAllocatorReport().?;
148 var stack_byte: u8 = 0;
149 allocator.rawFree((&stack_byte)[0..1], .@"1", @returnAddress());
150
151 const finish = process_owner.finish();
152 const report = finish.diagnosticReport() orelse return error.ExpectedAllocatorDiagnostics;
153 const issue = report.last_issue orelse return error.ExpectedAllocatorDiagnostics;
154 try std.testing.expectEqual(@as(u8, 1), finish.exitCode());
155 try std.testing.expect(report.counters.invalid_free >= before.counters.invalid_free + 1);
156 try std.testing.expectEqual(.invalid_free, issue.kind);
157 try std.testing.expectEqual(@intFromPtr(&stack_byte), issue.address);
158 try std.testing.expectEqual(config.mode, allocator_diagnostics_mode.?);
159 }
160
161 test "process allocator owner returns overflow diagnostics" {
162 const config = diagnosticAllocatorConfig() orelse return;
163 var process_owner = ProcessAllocatorOwner.init();
164 const allocator = process_owner.allocator();
165
166 const before = diagnosticAllocatorReport().?;
167 const ptr = allocator.rawAlloc(8, .@"1", @returnAddress()) orelse return error.OutOfMemory;
168 ptr[8] = 0xee;
169
170 const finish = process_owner.finish();
171 const report = finish.diagnosticReport() orelse return error.ExpectedAllocatorDiagnostics;
172 const issue = report.last_issue orelse return error.ExpectedAllocatorDiagnostics;
173 try std.testing.expectEqual(@as(u8, 1), finish.exitCode());
174 try std.testing.expect(report.counters.buffer_overflow >= before.counters.buffer_overflow + 1);
175 try std.testing.expect(report.live_allocations >= before.live_allocations + 1);
176 try std.testing.expectEqual(.buffer_overflow, issue.kind);
177 try std.testing.expectEqual(@intFromPtr(ptr), issue.address);
178 try std.testing.expectEqual(@as(usize, 8), issue.offset);
179 try std.testing.expectEqual(@as(usize, 8), issue.requested_len);
180 try std.testing.expect(issue.allocation_id != 0);
181 try std.testing.expectEqual(config.mode, allocator_diagnostics_mode.?);
182
183 allocator.rawFree(ptr[0..8], .@"1", @returnAddress());
184 }
185
186 test "process allocator phase lifecycle" {
187 switch (process_phase_mode) {
188 .off => {
189 try std.testing.expect(processAllocatorPhaseViolations() == null);
190 try std.testing.expect(processAllocatorPhaseViolationSites() == null);
191 try std.testing.expect(processAllocatorPhase() == null);
192 sealProcessAllocatorPhase();
193 beginProcessAllocatorTeardown();
194 },
195 .observe => {
196 const allocator = policy.platformProcessAllocator();
197 const startup = try allocator.alloc(u8, 24);
198 @memset(startup, 0x11);
199 const before = processAllocatorPhaseViolations().?;
200
201 sealProcessAllocatorPhase();
202 try std.testing.expectEqual(.steady, processAllocatorPhase().?);
203 const steady = try allocator.alloc(u8, 24);
204 @memset(steady, 0x22);
205 allocator.free(steady);
206 const during = processAllocatorPhaseViolations().?;
207 try std.testing.expect(during.allocations >= before.allocations + 1);
208 try std.testing.expect(during.frees >= before.frees + 1);
209 try std.testing.expect(processAllocatorPhaseViolationSites().?.count >= 1);
210
211 beginProcessAllocatorTeardown();
212 try std.testing.expectEqual(.teardown, processAllocatorPhase().?);
213 allocator.free(startup);
214 const after = processAllocatorPhaseViolations().?;
215 try std.testing.expectEqual(during.frees, after.frees);
216 },
217 .seal => {
218 const allocator = policy.platformProcessAllocator();
219 const startup = try allocator.alloc(u8, 24);
220 @memset(startup, 0x33);
221 try std.testing.expectEqual(.initialization, processAllocatorPhase().?);
222 try std.testing.expect(processAllocatorPhaseViolationSites() == null);
223
224 sealProcessAllocatorPhase();
225 try std.testing.expectEqual(.steady, processAllocatorPhase().?);
226 try std.testing.expectEqual(@as(u64, 0), processAllocatorPhaseViolations().?.total());
227
228 beginProcessAllocatorTeardown();
229 try std.testing.expectEqual(.teardown, processAllocatorPhase().?);
230 allocator.free(startup);
231 try std.testing.expectEqual(@as(u64, 0), processAllocatorPhaseViolations().?.total());
232 },
233 }
234 }
235
236 test "process allocator finish maps reports to exit codes" {
237 const clean: ProcessAllocatorFinish = .clean;
238 const leak: ProcessAllocatorFinish = .leak;
239 try std.testing.expectEqual(@as(u8, 0), clean.exitCode());
240 try std.testing.expectEqual(@as(u8, 1), leak.exitCode());
241 try std.testing.expectEqual(@as(u8, 1), (ProcessAllocatorFinish{ .diagnostics = .{ .counters = .{ .invalid_free = 1 } } }).exitCode());
242 try std.testing.expect((ProcessAllocatorFinish{ .diagnostics = .{} }).diagnosticReport() != null);
243 try std.testing.expect(clean.diagnosticReport() == null);
244 }