Skip to content
zit Artifact [8e8116c0b5]
Login

Artifact [8e8116c0b5]

Artifact 8e8116c0b57c1b6028cced8822e6ee9e3a8ffa684e75cfe51a554a25cf3daffe:


//! 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);
}