//! Source-text rules `zig build test` enforces by walking src/.
//!
//! 1. Every writer on an inherited stdout or stderr is a streaming writer.
//!
//! `std.Io.File.Writer.init` (and `File.writer`) picks `.positional` mode,
//! which writes with `pwrite` from its own offset that starts at 0. On a
//! standard stream that is wrong: the shell opened it with `>>` or shares it
//! across a `{ a; b; } > log` group, and a positional write lands at byte 0 of
//! that log, over whatever earlier commands wrote. A battery logging test, e2e
//! and smoke into one file lost every line before smoke's this way.
//! `initStreaming` uses plain `write`, which honours the inherited offset and
//! O_APPEND.
//!
//! 2. Every allocation-failure walk goes through
//! `no_grow_allocator.checkAllAllocationFailures`. The std version counts
//! only `alloc` calls, and an in-place resize that succeeds on one heap and
//! not another shifts every later index, so its counts depend on the heap.
const std = @import("std");
const repo_options = @import("repo_options");
const positional_forms = [_][]const u8{
".init(.stdout()",
".init(.stderr()",
".stdout().writer(",
".stderr().writer(",
};
/// The one place allowed to call the std allocation-failure walk directly.
const alloc_walk_form = "testing.checkAllAllocationFailures";
const alloc_walk_home = "no_grow_allocator.zig";
const this_file = "source_guard_test.zig";
/// The walk must see at least this many files, or it did not see the tree.
const min_expected_files = 40;
const max_source_bytes = 8 << 20;
test "source guard: streaming std writers, no direct std allocation-failure walk" {
const io = std.testing.io;
var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const src_abs = try std.fmt.allocPrint(arena, "{s}/src", .{repo_options.root});
var src_dir = std.Io.Dir.cwd().openDir(io, src_abs, .{ .iterate = true }) catch |err| switch (err) {
error.FileNotFound, error.NotDir => return error.SkipZigTest,
else => return err,
};
defer src_dir.close(io);
var scanned: usize = 0;
var violations: usize = 0;
var walker = try src_dir.walk(arena);
defer walker.deinit();
while (try walker.next(io)) |entry| {
if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".zig")) continue;
if (std.mem.eql(u8, entry.path, this_file)) continue;
scanned += 1;
const source = try src_dir.readFileAlloc(io, entry.path, arena, .limited(max_source_bytes));
var line_no: usize = 0;
var it = std.mem.splitScalar(u8, source, '\n');
while (it.next()) |line| {
line_no += 1;
for (positional_forms) |form| {
if (std.mem.indexOf(u8, line, form) == null) continue;
violations += 1;
std.debug.print(
"\nsource guard: src/{s}:{d} opens a positional writer on a standard stream; use initStreaming:\n {s}\n",
.{ entry.path, line_no, std.mem.trim(u8, line, " ") },
);
}
if (std.mem.indexOf(u8, line, alloc_walk_form) != null and !std.mem.eql(u8, entry.path, alloc_walk_home)) {
violations += 1;
std.debug.print(
"\nsource guard: src/{s}:{d} calls the std allocation-failure walk; use no_grow_allocator.checkAllAllocationFailures:\n {s}\n",
.{ entry.path, line_no, std.mem.trim(u8, line, " ") },
);
}
}
}
if (scanned < min_expected_files) {
std.debug.print("\nsource guard: scanned only {d} files under {s}; the walk is not seeing the tree\n", .{ scanned, src_abs });
return error.SourceGuardWalkFailed;
}
try std.testing.expectEqual(@as(usize, 0), violations);
}