पाठ 21 / 25
Testing in Zig
Write tests next to code with std.testing and detect leaks.
Tests are part of the language
Zig has built-in test blocks: test "name" { ... } declarations live in the same file as the code they test and are compiled only when running zig test file.zig or zig build test. Tests use std.testing: expect(condition), expectEqual(expected, actual), expectEqualStrings, expectEqualSlices, expectError(error.X, expr) and expectApproxEqAbs for floats; all return errors, so they are called with try. std.testing.allocator is a leak-detecting allocator: a test that forgets to free memory fails, which makes tests a strong check on manual memory management. std.testing.checkAllAllocationFailures runs a function repeatedly, failing each allocation in turn, to verify that error paths (and errdefer) clean up correctly. Tests can access private declarations in the same file. To include tests from other files, reference them, for example with test { std.testing.refAllDecls(@This()); } or _ = @import("other.zig"); inside a test block. Filter tests with --test-filter. Doc comments (///) and zig build-lib -femit-docs generate documentation.
Tests with leak detection and allocation-failure checks
std.testing.allocator and checkAllAllocationFailures.
const std = @import("std");
/// Splits a comma-separated list into an owned slice of trimmed fields.
/// Caller owns the outer slice (inner slices point into `input`).
pub fn splitFields(allocator: std.mem.Allocator, input: []const u8) ![][]const u8 {
var fields: std.ArrayList([]const u8) = .empty;
errdefer fields.deinit(allocator);
var it = std.mem.splitScalar(u8, input, ',');
while (it.next()) |raw| {
try fields.append(allocator, std.mem.trim(u8, raw, " "));
}
return fields.toOwnedSlice(allocator);
}
test "splits and trims fields" {
const fields = try splitFields(std.testing.allocator, "o-1, Pune ,120000");
defer std.testing.allocator.free(fields); // forgetting this makes the test fail with a leak
try std.testing.expectEqual(@as(usize, 3), fields.len);
try std.testing.expectEqualStrings("Pune", fields[1]);
}
test "single field" {
const fields = try splitFields(std.testing.allocator, "only");
defer std.testing.allocator.free(fields);
try std.testing.expectEqualSlices(u8, "only", fields[0]);
}
fn splitAndFree(allocator: std.mem.Allocator) !void {
const fields = try splitFields(allocator, "a,b,c,d,e");
allocator.free(fields);
}
test "no leaks when any allocation fails" {
try std.testing.checkAllAllocationFailures(std.testing.allocator, splitAndFree, .{});
}
// zig test src/fields.zig
// zig build test --summary allTest the error paths
checkAllAllocationFailures is one of Zig's best testing tools: it exercises every out-of-memory path, which is where errdefer mistakes hide, and it needs no extra test code.
त्वरित जाँच: What happens if a test using std.testing.allocator leaks memory?
- Nothing
- The program crashes in production
- The test fails and reports the leak
- Zig frees it silently
Answer
The test fails and reports the leak — The testing allocator tracks allocations and fails leaking tests.